39
EFFECTIVE C++ 정정 Chapter 3

Effective C++ 정리 chapter 3

  • Upload
    -

  • View
    158

  • Download
    4

Embed Size (px)

Citation preview

Page 1: Effective C++ 정리 chapter 3

EFFECTIVE C++ 정리Chapter 3

Page 2: Effective C++ 정리 chapter 3

자원관리는 전문가에게ITEM 13

Page 3: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• 자원이란 ?

• 시스템한테 받아오는 빚 (?) 같은것…

• 사용할 때 할당 받고 사용을 다하면 해제하는 것• 메모리 , 뮤텍스 , 파일 등등…

• New / Delete , Get / Release

• 확실하게 하지 않으면 자원 낭비 ( 누수 )

• 버릇을 들이는 것이 중요

• 혹은 전문가 ( 클래스 ) 에게 맡겨 주자 !!! ( 이번 장의 요지 )

Page 4: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• 투자 모델링 클래스를 만들어 보자• class Investment { …. };

• 팩토리 함수를 통해서 객체를 받아온다 .

• Investment* createInvestment();

• 그렇다면 해제는 받아온 사람이 해야겠지 ?

• Investment* pInv = createInvestment();

… //do Something

delete pInvestment;

Page 5: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• 과연 제대로 동작할까 ?

• 제대로 해제하려면 반드시 delete 가 호출

• ‘…’ 부분에서 중도 하차한다면 ?

• 중간에 return

• 중간에 throw

• 중간에 loop break;

• 해제가 제대로 되지 않을 엄청난 가능성 !!

Page 6: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• 자원 관리하는 객체를 사용하자 !

• 자원을 객체에 넣고 해제는 소멸자에

• 어떤 식이든 Scope 를 벗어나면 클래스는 소멸자를 호출하게된다 .

• void ResourceExample() {

ResourceManger rm; // 생성 …} // 어떤 식이든 scope 를 떠나면 소멸자가 호출된다 .

Page 7: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• auto_ptr 을 써보자 (c++98 기준 )

• 포인터와 비슷하게 동작하는 자원 관리 클래스 (smart pointer)

• 가리키는 대상에 대해서 소멸자가 delet 를 호출하는 방식

• void ResourceExample(){

std::auto_ptr<Investment> pInv( createInvestment() );

….

} // 소멸자 호출 == delete

Page 8: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• 예제 설명• 자원을 할당한 뒤 곧 . 바 . 로 자원관리 객체에게 넘겨주자 (RAII)

• createInvestment 로 자원을 만들고 auto_ptr 의 생성자로 넘겨준다 .

• 자원 획득 == 초기화 (Resource Acquisition is Initialization : RAII)

• 쓸데없이 다른 곳에 자원의 포인터가 넘어가는 것을 방지

• 하나의 자원은 하나의 자원 관리자에게만 !

• 소멸자 해제로 확실한 해제를 보장한다 .

• return/break/throw 뭐가되었건 scope 가 벗어나면 소멸자가 호출된다 .

Page 9: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• 관리 객체의 복사는 허용하지 않는다 .

• auto_ptr 에서 복사 생성자 또는 대입연산자를 호출하는 경우• 대상 객체에 정보를 넘기고 원본은 NULL 로 변한다 .

• 하나의 자원에는 하나의 자원관리자만 !

• 복사가 허용되지 않으면 일반적인 포인터랑은 조금 다른데요 ?

• 우리는 여기저기서 할당된 자원을 공유할 필요가 있다 .

• Scope 가 넘어가도 살아있었으면 좋겠어

• 그래서 좀더 기능이 많은 관리자들이 필요함 !

Page 10: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• 참조 카운팅 방식 스마트 포인터 (RCSP)

• 자원이 외부에서 몇번이나 사용되고 있는지를 체크

• 새로 받아서 쓰면 ++, 다 썼으면 –

• 0 이 되면 해제 (delete)

• Java 의 garbage collectio 의 동작방식 ( 개략적이지만 )

Page 11: Effective C++ 정리 chapter 3

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) 해제

Page 12: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• void ResourceExample(){

std::tr1::shared_ptr<Investment>

pInv( createInvestment() ); // 참조 카운트 ++

(1)

Other -> GetSP( pInv ); // 다른 객체에게 전달 // 카운트 ++ (2)

} // 참조 카운트 – (1) 삭제 안되고 다른 객체가 쓸수있다 .

Page 13: Effective C++ 정리 chapter 3

Item 13: 자원관리는 전문가에게

• 배열 자원은 auto_ptr / share_ptr 쓰지말자• auto/share_ptr 소멸자에서 단순 delete 만 수행 (c++ 98)

• 그러니까 delete [] 안된다

• 배열의 자원관리는 auto_ptr / shared_ptr 사용하면안된다 .

• std::vector / std::string 등의 컨테이너 클래스를 사용하자 .

Page 14: Effective C++ 정리 chapter 3

자원관리자 복사는 신중히ITEM 14

Page 15: Effective C++ 정리 chapter 3

Item 14 : 자원관리자 복사는 신중히

• 메모리 자원만 auto/share_ptr 가능하다 .

• auto/shared_ptr 은 소멸자에서 delete 만 수행 (c++ 98)

• 다른 자원 ( 뮤텍스 , 파일 , DB…etc) 은 어떻게 관리하지 ?

• 룰은 이해했으니 직접 만들자 .

• 뮤텍스라면 잠금 / 해제 == 자원획득 / 자원해제• void lock(Mutex* pm);

• void unlock(Mutex* pm);

Page 16: Effective C++ 정리 chapter 3

Item 14 : 자원관리자 복사는 신중히

• class Lock {

public:

explicit Lock( Mutex* pm ) : mutexPtr( pm )

{ lock( mutexPtr ); } // 생성자에서 자원 획득~Lock() { unlock ( mutexPtr ); } // 소멸자에서 자원 해제

private:

Mutex* mutexPtr;

}

Page 17: Effective C++ 정리 chapter 3

Item 14 : 자원관리자 복사는 신중히

• void LockExample(){

Mutex m;

{

Lock ml( &m ); // Lock 생성자 호출 : 자원 획득…

} // Lock 소멸자 호출 : 자원 해제}

Page 18: Effective C++ 정리 chapter 3

Item 14 : 자원관리자 복사는 신중히

• 문제는 Lock 자원 객체의 복사 ( 이번 아이템 주제 )

• 복사 생성자 , 대입연산자를 어떻게 구성할 것인가 !

• 뮤텍스를 복사하면 발생할 수 있는 수많은 문제들• 서로 다른 Lock 이 서로서로에게 영향을 받음

• 원하지 않은 타이밍에 락 해제가 된다면…

• 서로 다른 자원 관리자가 자원을 공유해선 안됨 !

Page 19: Effective C++ 정리 chapter 3

Item 14 : 자원관리자 복사는 신중히

• 복사 동작을 어떻게 할 것인가1. 복사를 금지한다

• 사본이 존재하면 안되는 경우 : ex) Mutex

• Item 6 에서 봉인술을 설명했으니 참조

2. 관리 자원의 참조 카운팅을 수행한다 .

• 사용자가 있는한 유지되어야 하는 자원인 경우

• 관리 자원의 포인터를 shared_ptr<T> 로 변경하면 쉽게 구현가능

• Shared_ptr 에서 제공하는 deleter 지정 ( 소멸자에서 호출할 함수지정 ) 을 활용하자 .

Page 20: Effective C++ 정리 chapter 3

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

}

Page 21: Effective C++ 정리 chapter 3

Item 14 : 자원관리자 복사는 신중히

• share_ptr 로 관리한다면 소멸자가 필요없다 .

• 참조 카운터가 0 이되면 관리 자원의 deleter 를 호출하니까 !

• Deleter 로 unlock 을 지정했다 .

• 참조 카운터가 0 이되면 unlock 이 호출된다 .

Page 22: Effective C++ 정리 chapter 3

Item 14 : 자원관리자 복사는 신중히

3. 관리 자원을 그냥 복사한다• 자원 관리 객체를 복사할 때는 깊은 복사를 수행하자 .

• 새로 자원을 할당 받은 뒤에 복사할 것

• string 을 복사한다고 가정• 단순히 주소만 복사한다면 같은 자원을 서로 다른 관리자가 물고있는 것

• 입 아프게 설명한 여러 문제들 발생 ( 두 번 해제 , 댕글링 포인터 등등 )

• 메모리를 따로 할당한 뒤 내용을 복사하자 .

Page 23: Effective C++ 정리 chapter 3

Item 14 : 자원관리자 복사는 신중히

4. 자원의 소유권을 이전한다 .

• 자원을 참조하는 객체가 딱 하나만 존재해야할 때

• auto_ptr 의 복사와 동일하게 • 기존 객체를 지우고 새객체에 옮긴다 .

• C++11 unique_ptr 훌륭하게 잘 만듦

Page 24: Effective C++ 정리 chapter 3

관리자원은 접근 가능하게ITEM 15

Page 25: Effective C++ 정리 chapter 3

Item 15 : 관리자원은 접근 가능하게

• 많은 API 들은 자원에 직접 접근하기를 원한다 .

• Int daysHeld ( const Investment* pi );

• tr1::std::shared_ptr<Investment> pInv; 를 사용해보자 .

• int day = daysHeld( pInv ); // 에러

• 함수가 원하는 건 자원의 포인터 !

• 직접 자원에 접근할 수 있는 get 함수를 활용한다 .

• Int day = daysHeld( pInv.get() ); // 잘 동작한다 .

Page 26: Effective C++ 정리 chapter 3

Item 15 : 관리자원은 접근 가능하게

• 역참조 연산자도 오버로딩되어있다 .

bool Investment::IsTexFree(); //Investment 클래스 멤버 함수…std::tr1::shared_ptr<Investment> pi1( createInvestment() );

bool texable1 = !(pi1->IsTaxFree() ); //operator ->

bool texable2 = !((*pi1).IsTaxFree() ); //operator *

Page 27: Effective C++ 정리 chapter 3

Item 15 : 관리자원은 접근 가능하게

• 자원관리자 자원 : 암시적 형변환의 지원• 명시적으로 getter 를 쓰는게 좋긴 한데 귀찮은 경우 .

• 암시적 변환 함수를 제공하면 된다 .Class Font{

public:

operator FontHandler() const { return f;}

private:

FontHandler f;

}

Page 28: Effective C++ 정리 chapter 3

Item 15 : 관리자원은 접근 가능하게

• 암시적 형변환은 실수의 여지가 많기 때문에 조심하자

void FontExample(FontHandler fh);

Font font1;

FontExample(font1); // 어이없는 형 변환 ,

// 댕글링 포인터 문제 야기

Page 29: Effective C++ 정리 chapter 3

new delete 형식 맞추자ITEM 16

Page 30: Effective C++ 정리 chapter 3

Item 16 : new delete 형식 맞추자

• New 동작 구조• 메모리 할당 (operator new)

• 할당된 메모리에 대해 한 개 이상의 생성자가 호출된다 .

• Delete 동작 구조• 대상 메모리에 대한 소멸자가 호출

• 메모리 해제 (operator delete)

Page 31: Effective C++ 정리 chapter 3

Item 16 : new delete 형식 맞추자

• 단일 객체 vs 배열• 메모리 구조가 다르다 .

• 배열의 크기 정보가 들어감

Page 32: Effective C++ 정리 chapter 3

Item 16 : new delete 형식 맞추자

• 배열 Delete 를 적용할 때 [] 를 써주자 .

• [] 는 이제 삭제하려는 것이 배열임을 명시하는 것 .

• 첫 번째 정보인 n 을 읽어서 배열 전체를 삭제한다 .

• 단일 객체 Delete 에 [] 를 쓴다면 ?

• 첫 번째 정보 n 을 읽으려고 한다… .

• 이상한 값을 읽어서 마구 지워버리겠지…

Page 33: Effective C++ 정리 chapter 3

Item 16 : new delete 형식 맞추자

• 간단한 규칙• New 에 [] 를 썼으면 ( 배열 생성이면 )

• Delete 에도 [] 를 써라… ( 제발 )

• 반대도 마찬가지 !

• 생성자에서 new / 소멸자에서 delete

• 다른 곳에서 하면 햇갈려서 실수하기 쉽다 .

Page 34: Effective C++ 정리 chapter 3

Item 16 : new delete 형식 맞추자

• typedef 를 사용한다면 배열인지 아닌지 명시해라typedef std::string AddressLines[4]; //AddressLines 는 string 배열타입

std::string* pal = new AddressLines; // 배열 생성자임을 잊지말자 .

delete pal; // 햇갈려서 이렇게 쓰면 큰일

delete [] pal; // 이렇게 써야된다 .

• 가능하면 배열은 typedef 하지말자… ( 제발 )

• std::vector 쓰면 간단하게 해결될 일

Page 35: Effective C++ 정리 chapter 3

자원 저장은 RAII 합시다ITEM 17

Page 36: Effective C++ 정리 chapter 3

Item 17 : 자원 저장은 RAII 합시다

• 예제 • Widget 객체에 우선순위에 따라 처리하는 함수

• void ProcessWidget(std::tr1::shared_ptr<Widget> pw, int priority);

• 우선순위 리턴 함수• Int Priority();

• 사용• ProcessWidget( new Wiget(), priority ); // 에러

Page 37: Effective C++ 정리 chapter 3

Item 17 : 자원 저장은 RAII 합시다

• shared_ptr 의 생성자는 explicit 로 선언되어있음• Widget* shared_ptr<Widget> 암묵적 형변환 안 된다 .

• 그래서• ProcessWidget(std::tr1::shared_ptr<Widget>(new Widget),

Priority());

• 하지만• 자원 누수의 가능성 !

Page 38: Effective C++ 정리 chapter 3

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 에서 오류 발생하면 ? 메모리 누수 !

Page 39: Effective C++ 정리 chapter 3

Item 17 : 자원 저장은 RAII 합시다

• 문제점• 자원 할당 시점과 자원 관리 객체로 넘어가는 시점 사이

• 다른 작업이 끼어들어 예외를 발생시켰기 때문

• RAII 하자고 ! (Resource Allocation is Initailization)

• 해결책• Std::tr1::shared_ptr<Widget> pw( new Widget ); // 자원 저장 별도

ProcessWidget( pw, Priority() ); // No leak, No stresss