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