Upload
-
View
42
Download
0
Embed Size (px)
Citation preview
Effective C++ Chapter 3 정리
Chapter 3 을 들어가면서
- 프로그래밍의 자원 = 사용을 마치고 돌려줘야 하는 모든 것
- ex) 동적 할당 메모리 , 뮤택스 , 브러쉬 등등
- 수작업으로 이 모든 자원들을 관리 한다 ?
- 객체 기반 방식의 자원관리로 문제 해결 !
Item 13
자원관리에는 객체가 좋다
Item 13 : 자원관리에는 객체가 좋다
예를 통해서 Item 13 을 공부하자
Class Missile {…}; // 미사일들의 최상위 클래스
Missile* createMissile(); // 미사일을 만들어 내는 팩토리 함수 ( 매개변수 생략 )
void f(){
Missile* pMissile = createMissile(); // 팩토리 함수 호출부… // pMissile 을 사용하는 부분이 있다고 하자delete pMissile; // 객체 해제
}
void f() 의 문제는 ???
Item 13 : 자원관리에는 객체가 좋다
예를 통해서 Item 13 을 공부하자
Class Missile {…}; // 미사일들의 최상위 클래스
Missile* createMissile(); // 미사일을 만들어 내는 팩토리 함수 ( 매개변수 생략 )
void f(){
Missile* pMissile = createMissile(); // 팩토리 함수 호출부… // pMissile 을 사용하는 부분이 있다고 하자delete pMissile; // 객체 해제
}
void f() 의 문제는 ???
Item 13 : 자원관리에는 객체가 좋다
delete 에 도달하지 못한채 pMissile 을 사용하는 부분에서 return 이 있었다면 ?goto 문이 있어서 delet 에 도달하지 못했다면 ?
아무튼 delete 에 도달하지 못했다면 ?
pMissile 에 할당된 메모리는 해제되지 못한다 . 즉 메모리 누수가 발생 !일단 이 문제를 auto_ptr 로 해결해 보자
void f() {
std::auto_ptr<Missile> pMissile(createMissile()); // 팩토리 함수를 호출
… // 어딘가에서 pMissile 사용} // auto_ptr 을 통해서 pMissile 의 delete 호출
Item 13 : 자원관리에는 객체가 좋다
자원관리에 객체를 사용하는 방법의 중요한 두 가지 특징
1. 자원 획득 후 바로 자원관리 객체에 넘기자
auto_ptr 의 예에서 createMissile() 의 반환 값인 Missile* 를 바로 자원관리 객체인auto_ptr 로 넘기고 있다 . 이는 하나의 자원관리 아이디어로 Resource Acquisi-tion is initialization(RAII) 이라 불린다 .
2. 자원 관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 해야 한다 .
위의 예에서 어떤 방식이든 상관 없이 f() 의 블록을 벗어나는 순간 auto_ptr 은 자신이 가리키고 있는 대상에 대해 delete 를 먹인다 . 따라서 auto_ptr 을 사용하지 않을 때의 걱정이사라진다 . 하지만 다른 걱정거리가 남는다 .
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을 사용할 수 없다 . 하지만 아쉽게도 컴파일 에러가 나지 않기 때문에 항상 이점은 주의하고 있어야한다 .
Item 14
자원관리 클래스의 복사 동작에 대해
진지하게 고찰하자
Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰
자원관리 객체의 사용과 복사
다음의 예를 통해 이번 항목을 설명하고자 한다
Class Lock{public:
explicit Lock(Mutex* pM) : mutexPtr(pM){ lock(mutexPtr); } // 자원 획득시 lock 을 건다
~Lock() { unlock(mutexPtr); } // Lock 객체 소멸시 mutex unlockprivate:
Mutex* mutexptr;}
Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰
실제 사용 방법
Mutex m; // mutex 객체 생성{ // 잠금 걸고 싶은 범위 설정을 위해 블록 만듦
Lock m1(&m); // 생성과 동시에 잠금…
}// 잠금해제
이렇게 사용할 수 있는 Lock 객체를 복사한다면 어떤 동작이 이루어져야 할까요 ?
Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰
1. 복사를 금지하자
RAII 객체가 복사되도록 놔두는 것 자체가 말이 안 되는 상황이 꽤 많다예를 들었던 Lock 객체의 경우 복사가 허용된다면 쓰레드 동기화를 위한` 열쇠 ` 가 두 개가 생기는 샘이니 사용자의 의도대로 동기화가 이루어지지 않을 수 있다
따라서 다음과 같은 복사 방지를 걸어 놓을 수 있다
class Lock : private Uncopyable // 항목 6 참조
Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰
2. 관리하고 있는 자원에 대한 참조 카운팅을 수행하자
자원을 사용하고 있는 객체가 소멸될 때까지 delete 를 호출하지 말아야 할 수도 있다 .이런 경우를 위해 shared_ptr 이 사용된다 .
그런데 shared_ptr 을 사용하면서 참조 카운트가 0 이 되었을 때 delete 가 호출되는 것이 아니라 다른 작업을 하고 싶을 수 있다 .
다른 작업을 해주는 함수 혹은 함수 객체를 삭제자라 하고 사용자는 삭제자를 지정할 수 있다 .
Item 14 : 자원관리 클래스의 복사 동작에 대한 고찰
3. 관리하고 있는 자원을 진짜로 복사
자원관리 객체와 함께 자원도 복사해야 하는 때이다 .
이 때는 ` 자원을 다 썼을 때 각각의 사본을 확실히 해제하는 것 ` 이 자원관리 객체의 명분이 된다 .
4. 관리 대상 자원의 소유권을 옮긴다
마치 auto_ptr 의 ` 복사 ` 동작처럼 , 즉 move 처럼 움직이는 경우
결론 : 객체 복사 함수는 컴파일러에 의해 생성될 여지가 있기 때문에 , 컴파일러가 생성한버전의 동작이 사용자가 원한 동작과 맞지 않을 때 객체 복사 함수를 직접 만들어야 한다 .
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;
}
Item 15
자원 관리 클래스에서 관리되는 자원은
외부 접근을 허용 하자
Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자
RAII 로 작동하는 자원관리 객체들을 통해서만 자원을 사용하지는 않는다 .
많은 legacy 코드들이 자원을 직접 참조하게 되어 있기 때문에 자원관리 객체는
실제 자원을 얻는 방식을 제공해야 한다 .
두 가지 방식이 있는데 명시적 변환과 암시적 변환이 그것이다 .
Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자
1. 명시적 변환
shared_ptr 과 auto_ptr 은 get() 이라는 함수를 제공한다 .
이는 자원관리 객체가 가리키고 있는 자원 , 실제 포인터 ( 의 사본 ) 을 얻을 수 있게 한다 .
Item 13 에서의 예를 가져와 보면
std::shared_ptr<Missile> pMissile(createMissile());
pMissile.get() 하면 실제 자원인 포인터를 얻을 수 있게 된다 .
Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자
2-1. 암묵적 변환 1
class Font{public:
explicit Font(FontHandle fh): f(fh){}
operator FontHandle() const{ return f; }…
private:FontHandle f;
};
Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자
2-1. 암묵적 변환 1
Font f(getFont()); // getFont 는 FontHandle 을 반환 int newFontSize = 10;…changeSize(f, newFontSize); // f 는 Font 이지만 operator 선언에 의해
// FontHandle 로 변환 된다
이런 방식이 있긴 하지만 코드 가독성이 떨어지기 때문에
사용하지 않는 것이 좋을지도 ..
Item 15 : 자원관리 클래스에서 관리되는 자원은 외부접근을 허용하자
2-2. 암묵적 변환 2
shared_ptr 과 auto_ptr 은 get() 이라는 역참조 방식을 지원한다 .
역참조 예std::shared_ptr<Missile> pMissile(createMissile());
*pMissile 을 하면 가리키고 있는 자원을 획득 할 수 있다 .
Item 16
new 및 delete 는 형태를 맞추자
Item 16 : new 및 delete 는 형태를 맞추자
new 및 delete 의 동작방식
어떤 객체를 new 로 할당하면 다음과 같은 과정을 거친다
1. 메모리가 할당 됨2. 할당한 메모리에 대한 한 개 이상의 생성자가 호출 됨
어떤 객체를 delete 로 해제하면 다음과 같은 과정을 거친다
3. 기존 할당된 메모리에 대한 한 개 이상의 소멸자가 호출 됨4. 메모리가 해제 됨
Item 16 : new 및 delete 는 형태를 맞추자
단일 객체 메모리 배치 구조
객체 배열에 대한 메모리 구조
객체 배열에 대한 메모리가 할당될 경우 할당된 객체가 몇 개인지에 대한 정보를 갖는다 .
new 는 단일객체 메모리 구조를 만들고
new[] 은 객체 배열에 대한 메모리 구조를 만든다
Object
N Object Object Object Object Object
Item 16 : new 및 delete 는 형태를 맞추자
따라서 delete 의 경우도 구분지어 사용해야 한다
delete 의 경우 단일 객체 메모리를 할당 해제 한다 .
따라서 포인터가 가리키는 맨 앞의 객체 하나만 할당 해주면 된다 .
delete[] 은 먼저 N 을 읽고 자신이 앞으로 몇 개의 객체에 대해 소멸자를 호출해 주어야 할지 안다 .
따라서 delete[] 을 사용해야 할 지점에 delete 를 사용한다면 알 수 없는 미정의 동작이 일어난다 .
Item 17
new 로 생성한 객체를 스마트 포인터에
저장하는 코드는 별도의 한문장으로
Item 17 : new 로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한 문장으로
이번 항목에서는 ` 별도 ` 라는 표현이 중요합니다 .
미사일에 랜덤한 데미지를 부여하는 함수가 있다고 합시다 .
int getRandomDamage();
void setRandomDamage(std::shared_ptr<Missile> pM, getRandomDam-age());
사용은 다음과 같이 할 수 있습니다 .
setRandomDamage(std::shared_ptr<Missile>(new Missile), getRandomDamge());
Item 17 : new 로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한 문장으로
setRandomDamage(std::shared_ptr<Missile>(new Missile), getRandomDamge());
이 함수의 문제는 함수의 인자에서 찾을 수 있습니다 . 인자에서는 세 가지 일이 일어납니다 .
1. getRandomDamage 호출2. “new Missile” 실행3. shared_ptr 생성자 호출
문제는 이 세 가지 일의 실행 순서가 컴파일러에 따라 다르다는 것입니다 .
Item 17 : new 로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한 문장으로
만약 다음과 같은 순서로 인자의 실행 순서가 결정 되면 문제가 생길 수 있습니다 .
1. “new Missile” 실행2. getRandomDamage 호출 3. shared_ptr 생성자 호출
만약 new Missile 을 실행한 이후 getRandomDamage 에서 예외가 발생하면 new Missile 의 포인터는 유실 될 수 있습니다 .
이 문제를 피하기 위해서는 별도의 코드에서 스마트 포인터에 객체를 저장하면 됩니다 .
Item 17 : new 로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한 문장으로
함수의 실행 전에 스마트 포인트로 객체를 저장하는 것입니다 .
std::shared_ptr<Missile> pM(new Missile); // 객체 저장후
setRandomDamage(pM, getRandomDagage()); // 함수 호출
이렇게 하면 인자의 설정시 생길 수 있는 문제를 미연에 방지할 수 있게 됩니다 .