30
Effective C++ Chapter 3 정정

Effective c++chapter3

  • Upload
    -

  • View
    42

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Effective c++chapter3

Effective C++ Chapter 3 정리

Page 2: Effective c++chapter3

Chapter 3 을 들어가면서

- 프로그래밍의 자원 = 사용을 마치고 돌려줘야 하는 모든 것

- ex) 동적 할당 메모리 , 뮤택스 , 브러쉬 등등

- 수작업으로 이 모든 자원들을 관리 한다 ?

- 객체 기반 방식의 자원관리로 문제 해결 !

Page 3: Effective c++chapter3

Item 13

자원관리에는 객체가 좋다

Page 4: Effective c++chapter3

Item 13 : 자원관리에는 객체가 좋다

예를 통해서 Item 13 을 공부하자

Class Missile {…}; // 미사일들의 최상위 클래스

Missile* createMissile(); // 미사일을 만들어 내는 팩토리 함수 ( 매개변수 생략 )

void f(){

Missile* pMissile = createMissile(); // 팩토리 함수 호출부… // pMissile 을 사용하는 부분이 있다고 하자delete pMissile; // 객체 해제

}

void f() 의 문제는 ???

Page 5: Effective c++chapter3

Item 13 : 자원관리에는 객체가 좋다

예를 통해서 Item 13 을 공부하자

Class Missile {…}; // 미사일들의 최상위 클래스

Missile* createMissile(); // 미사일을 만들어 내는 팩토리 함수 ( 매개변수 생략 )

void f(){

Missile* pMissile = createMissile(); // 팩토리 함수 호출부… // pMissile 을 사용하는 부분이 있다고 하자delete pMissile; // 객체 해제

}

void f() 의 문제는 ???

Page 6: Effective c++chapter3

Item 13 : 자원관리에는 객체가 좋다

delete 에 도달하지 못한채 pMissile 을 사용하는 부분에서 return 이 있었다면 ?goto 문이 있어서 delet 에 도달하지 못했다면 ?

아무튼 delete 에 도달하지 못했다면 ?

pMissile 에 할당된 메모리는 해제되지 못한다 . 즉 메모리 누수가 발생 !일단 이 문제를 auto_ptr 로 해결해 보자

void f() {

std::auto_ptr<Missile> pMissile(createMissile()); // 팩토리 함수를 호출

… // 어딘가에서 pMissile 사용} // auto_ptr 을 통해서 pMissile 의 delete 호출

Page 7: Effective c++chapter3

Item 13 : 자원관리에는 객체가 좋다

자원관리에 객체를 사용하는 방법의 중요한 두 가지 특징

1. 자원 획득 후 바로 자원관리 객체에 넘기자

auto_ptr 의 예에서 createMissile() 의 반환 값인 Missile* 를 바로 자원관리 객체인auto_ptr 로 넘기고 있다 . 이는 하나의 자원관리 아이디어로 Resource Acquisi-tion is initialization(RAII) 이라 불린다 .

2. 자원 관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 해야 한다 .

위의 예에서 어떤 방식이든 상관 없이 f() 의 블록을 벗어나는 순간 auto_ptr 은 자신이 가리키고 있는 대상에 대해 delete 를 먹인다 . 따라서 auto_ptr 을 사용하지 않을 때의 걱정이사라진다 . 하지만 다른 걱정거리가 남는다 .

Page 8: Effective c++chapter3

Item 13 : 자원관리에는 객체가 좋다

auto_ptr 의 걱정거리

auto_ptr 은 소멸시 바로 가리키고 있는 객체에 delete 를 먹이기 때문에 어떤 객체를 가리키는 auto_ptr 이 두 개 이상이 되면 안 된다 . 따라서 auto_ptr 을 복사하려고 하면 원본은 null 이 된다 . 마치 move 처럼 작동 한다 .

이는 shared_ptr 을 통해서 해결 할 수 있지만 자세히 다루는 것은 다른 Item 에서 하겠다 .

auto_ptr 이나 shared_ptr 의 다른 걱정거리

auto_ptr 이나 shared_ptr 은 소멸자에서 가리키는 대상에 대해 항상 delet 를 호출한다 . delete[] 는 호출하지 않는다 . 즉 동적배열에 대해서는 auto_ptr 이나 shared_ptr을 사용할 수 없다 . 하지만 아쉽게도 컴파일 에러가 나지 않기 때문에 항상 이점은 주의하고 있어야한다 .

Page 9: Effective c++chapter3

Item 14

자원관리 클래스의 복사 동작에 대해

진지하게 고찰하자

Page 10: Effective c++chapter3

Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰

자원관리 객체의 사용과 복사

다음의 예를 통해 이번 항목을 설명하고자 한다

Class Lock{public:

explicit Lock(Mutex* pM) : mutexPtr(pM){ lock(mutexPtr); } // 자원 획득시 lock 을 건다

~Lock() { unlock(mutexPtr); } // Lock 객체 소멸시 mutex unlockprivate:

Mutex* mutexptr;}

Page 11: Effective c++chapter3

Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰

실제 사용 방법

Mutex m; // mutex 객체 생성{ // 잠금 걸고 싶은 범위 설정을 위해 블록 만듦

Lock m1(&m); // 생성과 동시에 잠금…

}// 잠금해제

이렇게 사용할 수 있는 Lock 객체를 복사한다면 어떤 동작이 이루어져야 할까요 ?

Page 12: Effective c++chapter3

Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰

1. 복사를 금지하자

RAII 객체가 복사되도록 놔두는 것 자체가 말이 안 되는 상황이 꽤 많다예를 들었던 Lock 객체의 경우 복사가 허용된다면 쓰레드 동기화를 위한` 열쇠 ` 가 두 개가 생기는 샘이니 사용자의 의도대로 동기화가 이루어지지 않을 수 있다

따라서 다음과 같은 복사 방지를 걸어 놓을 수 있다

class Lock : private Uncopyable // 항목 6 참조

Page 13: Effective c++chapter3

Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰

2. 관리하고 있는 자원에 대한 참조 카운팅을 수행하자

자원을 사용하고 있는 객체가 소멸될 때까지 delete 를 호출하지 말아야 할 수도 있다 .이런 경우를 위해 shared_ptr 이 사용된다 .

그런데 shared_ptr 을 사용하면서 참조 카운트가 0 이 되었을 때 delete 가 호출되는 것이 아니라 다른 작업을 하고 싶을 수 있다 .

다른 작업을 해주는 함수 혹은 함수 객체를 삭제자라 하고 사용자는 삭제자를 지정할 수 있다 .

Page 14: Effective c++chapter3

Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰

3. 관리하고 있는 자원을 진짜로 복사

자원관리 객체와 함께 자원도 복사해야 하는 때이다 .

이 때는 ` 자원을 다 썼을 때 각각의 사본을 확실히 해제하는 것 ` 이 자원관리 객체의 명분이 된다 .

4. 관리 대상 자원의 소유권을 옮긴다

마치 auto_ptr 의 ` 복사 ` 동작처럼 , 즉 move 처럼 움직이는 경우

결론 : 객체 복사 함수는 컴파일러에 의해 생성될 여지가 있기 때문에 , 컴파일러가 생성한버전의 동작이 사용자가 원한 동작과 맞지 않을 때 객체 복사 함수를 직접 만들어야 한다 .

Page 15: Effective c++chapter3

Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰

삭제자 예시 코드

Class Lock{public:

explicit Lock(Mutex* pM) : mutexPtr(pM, unlock) // 삭제자로 unlock 을 지정해 주었다{ lock(mutexPtr.get()); } // 항목 15 참조 , 가리키는 대상 반환

// 소멸자는 더 이상 필요하지 않습니다 . // ~Lock() { unlock(mutexPtr);

private:std::shared_ptr<Mutex> mutexptr;

}

Page 16: Effective c++chapter3

Item 15

자원 관리 클래스에서 관리되는 자원은

외부 접근을 허용 하자

Page 17: Effective c++chapter3

Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자

RAII 로 작동하는 자원관리 객체들을 통해서만 자원을 사용하지는 않는다 .

많은 legacy 코드들이 자원을 직접 참조하게 되어 있기 때문에 자원관리 객체는

실제 자원을 얻는 방식을 제공해야 한다 .

두 가지 방식이 있는데 명시적 변환과 암시적 변환이 그것이다 .

Page 18: Effective c++chapter3

Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자

1. 명시적 변환

shared_ptr 과 auto_ptr 은 get() 이라는 함수를 제공한다 .

이는 자원관리 객체가 가리키고 있는 자원 , 실제 포인터 ( 의 사본 ) 을 얻을 수 있게 한다 .

Item 13 에서의 예를 가져와 보면

std::shared_ptr<Missile> pMissile(createMissile());

pMissile.get() 하면 실제 자원인 포인터를 얻을 수 있게 된다 .

Page 19: Effective c++chapter3

Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자

2-1. 암묵적 변환 1

class Font{public:

explicit Font(FontHandle fh): f(fh){}

operator FontHandle() const{ return f; }…

private:FontHandle f;

};

Page 20: Effective c++chapter3

Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자

2-1. 암묵적 변환 1

Font f(getFont()); // getFont 는 FontHandle 을 반환 int newFontSize = 10;…changeSize(f, newFontSize); // f 는 Font 이지만 operator 선언에 의해

// FontHandle 로 변환 된다

이런 방식이 있긴 하지만 코드 가독성이 떨어지기 때문에

사용하지 않는 것이 좋을지도 ..

Page 21: Effective c++chapter3

Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자

2-2. 암묵적 변환 2

shared_ptr 과 auto_ptr 은 get() 이라는 역참조 방식을 지원한다 .

역참조 예std::shared_ptr<Missile> pMissile(createMissile());

*pMissile 을 하면 가리키고 있는 자원을 획득 할 수 있다 .

Page 22: Effective c++chapter3

Item 16

new 및 delete 는 형태를 맞추자

Page 23: Effective c++chapter3

Item 16 : new 및 delete 는 형태를 맞추자

new 및 delete 의 동작방식

어떤 객체를 new 로 할당하면 다음과 같은 과정을 거친다

1. 메모리가 할당 됨2. 할당한 메모리에 대한 한 개 이상의 생성자가 호출 됨

어떤 객체를 delete 로 해제하면 다음과 같은 과정을 거친다

3. 기존 할당된 메모리에 대한 한 개 이상의 소멸자가 호출 됨4. 메모리가 해제 됨

Page 24: Effective c++chapter3

Item 16 : new 및 delete 는 형태를 맞추자

단일 객체 메모리 배치 구조

객체 배열에 대한 메모리 구조

객체 배열에 대한 메모리가 할당될 경우 할당된 객체가 몇 개인지에 대한 정보를 갖는다 .

new 는 단일객체 메모리 구조를 만들고

new[] 은 객체 배열에 대한 메모리 구조를 만든다

Object

N Object Object Object Object Object

Page 25: Effective c++chapter3

Item 16 : new 및 delete 는 형태를 맞추자

따라서 delete 의 경우도 구분지어 사용해야 한다

delete 의 경우 단일 객체 메모리를 할당 해제 한다 .

따라서 포인터가 가리키는 맨 앞의 객체 하나만 할당 해주면 된다 .

delete[] 은 먼저 N 을 읽고 자신이 앞으로 몇 개의 객체에 대해 소멸자를 호출해 주어야 할지 안다 .

따라서 delete[] 을 사용해야 할 지점에 delete 를 사용한다면 알 수 없는 미정의 동작이 일어난다 .

Page 26: Effective c++chapter3

Item 17

new 로 생성한 객체를 스마트 포인터에

저장하는 코드는 별도의 한문장으로

Page 27: Effective c++chapter3

Item 17 : new 로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한 문장으로

이번 항목에서는 ` 별도 ` 라는 표현이 중요합니다 .

미사일에 랜덤한 데미지를 부여하는 함수가 있다고 합시다 .

int getRandomDamage();

void setRandomDamage(std::shared_ptr<Missile> pM, getRandomDam-age());

사용은 다음과 같이 할 수 있습니다 .

setRandomDamage(std::shared_ptr<Missile>(new Missile), getRandomDamge());

Page 28: Effective c++chapter3

Item 17 : new 로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한 문장으로

setRandomDamage(std::shared_ptr<Missile>(new Missile), getRandomDamge());

이 함수의 문제는 함수의 인자에서 찾을 수 있습니다 . 인자에서는 세 가지 일이 일어납니다 .

1. getRandomDamage 호출2. “new Missile” 실행3. shared_ptr 생성자 호출

문제는 이 세 가지 일의 실행 순서가 컴파일러에 따라 다르다는 것입니다 .

Page 29: Effective c++chapter3

Item 17 : new 로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한 문장으로

만약 다음과 같은 순서로 인자의 실행 순서가 결정 되면 문제가 생길 수 있습니다 .

1. “new Missile” 실행2. getRandomDamage 호출 3. shared_ptr 생성자 호출

만약 new Missile 을 실행한 이후 getRandomDamage 에서 예외가 발생하면 new Missile 의 포인터는 유실 될 수 있습니다 .

이 문제를 피하기 위해서는 별도의 코드에서 스마트 포인터에 객체를 저장하면 됩니다 .

Page 30: Effective c++chapter3

Item 17 : new 로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한 문장으로

함수의 실행 전에 스마트 포인트로 객체를 저장하는 것입니다 .

std::shared_ptr<Missile> pM(new Missile); // 객체 저장후

setRandomDamage(pM, getRandomDagage()); // 함수 호출

이렇게 하면 인자의 설정시 생길 수 있는 문제를 미연에 방지할 수 있게 됩니다 .