effective c++ 정리 chapter 3
TRANSCRIPT
EFFECTIVE C++ 정리Chapter 3
자원관리는 전문가에게ITEM 13
Item 13: 자원관리는 전문가에게
• 자원이란 ?
• 시스템한테 받아오는 빚 (?) 같은것…
• 사용할 때 할당 받고 사용을 다하면 해제하는 것• 메모리 , 뮤텍스 , 파일 등등…
• New / Delete , Get / Release
• 확실하게 하지 않으면 자원 낭비 ( 누수 )
• 버릇을 들이는 것이 중요
• 혹은 전문가 ( 클래스 ) 에게 맡겨 주자 !!! ( 이번 장의 요지 )
Item 13: 자원관리는 전문가에게
• 투자 모델링 클래스를 만들어 보자• class Investment { …. };
• 팩토리 함수를 통해서 객체를 받아온다 .
• Investment* createInvestment();
• 그렇다면 해제는 받아온 사람이 해야겠지 ?
• Investment* pInv = createInvestment();
… //do Something
delete pInvestment;
Item 13: 자원관리는 전문가에게
• 과연 제대로 동작할까 ?
• 제대로 해제하려면 반드시 delete 가 호출
• ‘…’ 부분에서 중도 하차한다면 ?
• 중간에 return
• 중간에 throw
• 중간에 loop break;
• 해제가 제대로 되지 않을 엄청난 가능성 !!
Item 13: 자원관리는 전문가에게
• 자원 관리하는 객체를 사용하자 !
• 자원을 객체에 넣고 해제는 소멸자에
• 어떤 식이든 Scope 를 벗어나면 클래스는 소멸자를 호출하게된다 .
• void ResourceExample() {
ResourceManger rm; // 생성 …} // 어떤 식이든 scope 를 떠나면 소멸자가 호출된다 .
Item 13: 자원관리는 전문가에게
• auto_ptr 을 써보자 (c++98 기준 )
• 포인터와 비슷하게 동작하는 자원 관리 클래스 (smart pointer)
• 가리키는 대상에 대해서 소멸자가 delet 를 호출하는 방식
• void ResourceExample(){
std::auto_ptr<Investment> pInv( createInvestment() );
….
} // 소멸자 호출 == delete
Item 13: 자원관리는 전문가에게
• 예제 설명• 자원을 할당한 뒤 곧 . 바 . 로 자원관리 객체에게 넘겨주자 (RAII)
• createInvestment 로 자원을 만들고 auto_ptr 의 생성자로 넘겨준다 .
• 자원 획득 == 초기화 (Resource Acquisition is Initialization : RAII)
• 쓸데없이 다른 곳에 자원의 포인터가 넘어가는 것을 방지
• 하나의 자원은 하나의 자원 관리자에게만 !
• 소멸자 해제로 확실한 해제를 보장한다 .
• return/break/throw 뭐가되었건 scope 가 벗어나면 소멸자가 호출된다 .
Item 13: 자원관리는 전문가에게
• 관리 객체의 복사는 허용하지 않는다 .
• auto_ptr 에서 복사 생성자 또는 대입연산자를 호출하는 경우• 대상 객체에 정보를 넘기고 원본은 NULL 로 변한다 .
• 하나의 자원에는 하나의 자원관리자만 !
• 복사가 허용되지 않으면 일반적인 포인터랑은 조금 다른데요 ?
• 우리는 여기저기서 할당된 자원을 공유할 필요가 있다 .
• Scope 가 넘어가도 살아있었으면 좋겠어
• 그래서 좀더 기능이 많은 관리자들이 필요함 !
Item 13: 자원관리는 전문가에게
• 참조 카운팅 방식 스마트 포인터 (RCSP)
• 자원이 외부에서 몇번이나 사용되고 있는지를 체크
• 새로 받아서 쓰면 ++, 다 썼으면 –
• 0 이 되면 해제 (delete)
• Java 의 garbage collectio 의 동작방식 ( 개략적이지만 )
Item 13: 자원관리는 전문가에게
• tr1::shared_ptr (c++11 std::shared_ptr)
• Item 54 에서 자세히 다룰예정이니 여기서는 너무 기대말자
• Garbage Collection 이랑 달리 순환참조는 알아서 체크해야됨
• void ResourceExample(){
std::tr1::shared_ptr<Investment>
pInv( createInvestment() ); // 참조 카운트 ++ (1)
} // 참조 카운트 -- (0) 해제
Item 13: 자원관리는 전문가에게
• void ResourceExample(){
std::tr1::shared_ptr<Investment>
pInv( createInvestment() ); // 참조 카운트 ++
(1)
…
Other -> GetSP( pInv ); // 다른 객체에게 전달 // 카운트 ++ (2)
} // 참조 카운트 – (1) 삭제 안되고 다른 객체가 쓸수있다 .
Item 13: 자원관리는 전문가에게
• 배열 자원은 auto_ptr / share_ptr 쓰지말자• auto/share_ptr 소멸자에서 단순 delete 만 수행 (c++ 98)
• 그러니까 delete [] 안된다
• 배열의 자원관리는 auto_ptr / shared_ptr 사용하면안된다 .
• std::vector / std::string 등의 컨테이너 클래스를 사용하자 .
자원관리자 복사는 신중히ITEM 14
Item 14 : 자원관리자 복사는 신중히
• 메모리 자원만 auto/share_ptr 가능하다 .
• auto/shared_ptr 은 소멸자에서 delete 만 수행 (c++ 98)
• 다른 자원 ( 뮤텍스 , 파일 , DB…etc) 은 어떻게 관리하지 ?
• 룰은 이해했으니 직접 만들자 .
• 뮤텍스라면 잠금 / 해제 == 자원획득 / 자원해제• void lock(Mutex* pm);
• void unlock(Mutex* pm);
Item 14 : 자원관리자 복사는 신중히
• class Lock {
public:
explicit Lock( Mutex* pm ) : mutexPtr( pm )
{ lock( mutexPtr ); } // 생성자에서 자원 획득~Lock() { unlock ( mutexPtr ); } // 소멸자에서 자원 해제
private:
Mutex* mutexPtr;
}
Item 14 : 자원관리자 복사는 신중히
• void LockExample(){
Mutex m;
…
{
Lock ml( &m ); // Lock 생성자 호출 : 자원 획득…
} // Lock 소멸자 호출 : 자원 해제}
Item 14 : 자원관리자 복사는 신중히
• 문제는 Lock 자원 객체의 복사 ( 이번 아이템 주제 )
• 복사 생성자 , 대입연산자를 어떻게 구성할 것인가 !
• 뮤텍스를 복사하면 발생할 수 있는 수많은 문제들• 서로 다른 Lock 이 서로서로에게 영향을 받음
• 원하지 않은 타이밍에 락 해제가 된다면…
• 서로 다른 자원 관리자가 자원을 공유해선 안됨 !
Item 14 : 자원관리자 복사는 신중히
• 복사 동작을 어떻게 할 것인가1. 복사를 금지한다
• 사본이 존재하면 안되는 경우 : ex) Mutex
• Item 6 에서 봉인술을 설명했으니 참조
2. 관리 자원의 참조 카운팅을 수행한다 .
• 사용자가 있는한 유지되어야 하는 자원인 경우
• 관리 자원의 포인터를 shared_ptr<T> 로 변경하면 쉽게 구현가능
• Shared_ptr 에서 제공하는 deleter 지정 ( 소멸자에서 호출할 함수지정 ) 을 활용하자 .
Item 14 : 자원관리자 복사는 신중히• class Lock {
public:
explicit Lock( Mutex* pm )
: mutexPtr( pm , unlock ) //deleter 로 unlock 등록{ lock( mutexPtr.get() ); } // 생성자에서 자원 획득
private:
std::tr1::shared_ptr<Mutex> mutexPtr; //shared_ptr
}
Item 14 : 자원관리자 복사는 신중히
• share_ptr 로 관리한다면 소멸자가 필요없다 .
• 참조 카운터가 0 이되면 관리 자원의 deleter 를 호출하니까 !
• Deleter 로 unlock 을 지정했다 .
• 참조 카운터가 0 이되면 unlock 이 호출된다 .
Item 14 : 자원관리자 복사는 신중히
3. 관리 자원을 그냥 복사한다• 자원 관리 객체를 복사할 때는 깊은 복사를 수행하자 .
• 새로 자원을 할당 받은 뒤에 복사할 것
• string 을 복사한다고 가정• 단순히 주소만 복사한다면 같은 자원을 서로 다른 관리자가 물고있는 것
• 입 아프게 설명한 여러 문제들 발생 ( 두 번 해제 , 댕글링 포인터 등등 )
• 메모리를 따로 할당한 뒤 내용을 복사하자 .
Item 14 : 자원관리자 복사는 신중히
4. 자원의 소유권을 이전한다 .
• 자원을 참조하는 객체가 딱 하나만 존재해야할 때
• auto_ptr 의 복사와 동일하게 • 기존 객체를 지우고 새객체에 옮긴다 .
• C++11 unique_ptr 훌륭하게 잘 만듦
관리자원은 접근 가능하게ITEM 15
Item 15 : 관리자원은 접근 가능하게
• 많은 API 들은 자원에 직접 접근하기를 원한다 .
• Int daysHeld ( const Investment* pi );
• tr1::std::shared_ptr<Investment> pInv; 를 사용해보자 .
• int day = daysHeld( pInv ); // 에러
• 함수가 원하는 건 자원의 포인터 !
• 직접 자원에 접근할 수 있는 get 함수를 활용한다 .
• Int day = daysHeld( pInv.get() ); // 잘 동작한다 .
Item 15 : 관리자원은 접근 가능하게
• 역참조 연산자도 오버로딩되어있다 .
bool Investment::IsTexFree(); //Investment 클래스 멤버 함수…std::tr1::shared_ptr<Investment> pi1( createInvestment() );
bool texable1 = !(pi1->IsTaxFree() ); //operator ->
bool texable2 = !((*pi1).IsTaxFree() ); //operator *
Item 15 : 관리자원은 접근 가능하게
• 자원관리자 자원 : 암시적 형변환의 지원• 명시적으로 getter 를 쓰는게 좋긴 한데 귀찮은 경우 .
• 암시적 변환 함수를 제공하면 된다 .Class Font{
public:
operator FontHandler() const { return f;}
private:
FontHandler f;
}
Item 15 : 관리자원은 접근 가능하게
• 암시적 형변환은 실수의 여지가 많기 때문에 조심하자
void FontExample(FontHandler fh);
…
Font font1;
FontExample(font1); // 어이없는 형 변환 ,
// 댕글링 포인터 문제 야기
new delete 형식 맞추자ITEM 16
Item 16 : new delete 형식 맞추자
• New 동작 구조• 메모리 할당 (operator new)
• 할당된 메모리에 대해 한 개 이상의 생성자가 호출된다 .
• Delete 동작 구조• 대상 메모리에 대한 소멸자가 호출
• 메모리 해제 (operator delete)
Item 16 : new delete 형식 맞추자
• 단일 객체 vs 배열• 메모리 구조가 다르다 .
• 배열의 크기 정보가 들어감
Item 16 : new delete 형식 맞추자
• 배열 Delete 를 적용할 때 [] 를 써주자 .
• [] 는 이제 삭제하려는 것이 배열임을 명시하는 것 .
• 첫 번째 정보인 n 을 읽어서 배열 전체를 삭제한다 .
• 단일 객체 Delete 에 [] 를 쓴다면 ?
• 첫 번째 정보 n 을 읽으려고 한다… .
• 이상한 값을 읽어서 마구 지워버리겠지…
Item 16 : new delete 형식 맞추자
• 간단한 규칙• New 에 [] 를 썼으면 ( 배열 생성이면 )
• Delete 에도 [] 를 써라… ( 제발 )
• 반대도 마찬가지 !
• 생성자에서 new / 소멸자에서 delete
• 다른 곳에서 하면 햇갈려서 실수하기 쉽다 .
Item 16 : new delete 형식 맞추자
• typedef 를 사용한다면 배열인지 아닌지 명시해라typedef std::string AddressLines[4]; //AddressLines 는 string 배열타입
std::string* pal = new AddressLines; // 배열 생성자임을 잊지말자 .
…
delete pal; // 햇갈려서 이렇게 쓰면 큰일
delete [] pal; // 이렇게 써야된다 .
• 가능하면 배열은 typedef 하지말자… ( 제발 )
• std::vector 쓰면 간단하게 해결될 일
자원 저장은 RAII 합시다ITEM 17
Item 17 : 자원 저장은 RAII 합시다
• 예제 • Widget 객체에 우선순위에 따라 처리하는 함수
• void ProcessWidget(std::tr1::shared_ptr<Widget> pw, int priority);
• 우선순위 리턴 함수• Int Priority();
• 사용• ProcessWidget( new Wiget(), priority ); // 에러
Item 17 : 자원 저장은 RAII 합시다
• shared_ptr 의 생성자는 explicit 로 선언되어있음• Widget* shared_ptr<Widget> 암묵적 형변환 안 된다 .
• 그래서• ProcessWidget(std::tr1::shared_ptr<Widget>(new Widget),
Priority());
• 하지만• 자원 누수의 가능성 !
Item 17 : 자원 저장은 RAII 합시다
• 왜때문인가 ?
• ProcessWidget(std::tr1::shared_ptr<Widget>(new
Widget),Priority())
• 우리가 기대하는 동작 순서
• new Widget 실행 tr1::shared_ptr 생성자 호출 Priority 계산
• 하지만 이 셋의 연산 순서가 컴파일러마다 다르다 !
• 최악의 경우• New Widget Priority 호출 tr1::std::shared_ptr 생성자 호출
• Priority 에서 오류 발생하면 ? 메모리 누수 !
Item 17 : 자원 저장은 RAII 합시다
• 문제점• 자원 할당 시점과 자원 관리 객체로 넘어가는 시점 사이
• 다른 작업이 끼어들어 예외를 발생시켰기 때문
• RAII 하자고 ! (Resource Allocation is Initailization)
• 해결책• Std::tr1::shared_ptr<Widget> pw( new Widget ); // 자원 저장 별도
ProcessWidget( pw, Priority() ); // No leak, No stresss