[Effective C++ 3판] Chapter 3. 자원 관리 (항목 13~17)

Chapter 3. 자원 관리

정보처리산업기사 시험 & 시골에 제사지내러 내려가느라 포스팅이 늦었습니다 ㅠㅜ
이번 Chapter는 자원 관리입니다. C++의 자원 관리 철학은 RAII이죠~ 객체가 생성되고 소멸될 때 생성자와 소멸자가 호출되는 원리를 이용하여 자원과 객체의 life cycle을 동일시함으로서 더욱 더 편리하고 안정적으로 자원 관리를 할 수 있습니다!

항목 13 : 자원 관리에는 객체가 그만!

std::shared_ptr의 소멸자는 기본적으로 내부적으로 delete를 사용합니다. (delete[]가 아니라) 그래서 동적할당된 배열에 대해 std::shared_ptr를 사용하면 안됩니다. 대신에 std::vector나 C++11의 std::array 를 사용함으로서 raw array를 대신하는 방법이 있습니다. 또는 std::shared_ptr의 생성자에 custom deleter를 등록하는 방법이 있습니다.
http://stackoverflow.com/questions/13061979/shared-ptr-to-an-array-should-it-be-used

[ 이것만은 잊지 말자! ]
- 자원 누출을 막기 위해, 생성자 안에서 자원을 획득하고 소멸자에서 그것을 해제하는 RAII객체를 사용합시다.
- 일반적으로 널리 쓰이는 RAII 클래스는 tr1::shared_ptr (C++11부터 표준이 되어 std::shared_ptr로 쓰면 된다.) 그리고 auto_ptr (C++11부터 deprecated되었다. 대신에 대체재로서 std::unique_ptr를 사용하면 된다.) 입니다.

항목 14 : 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자.

RAII객체가 복사될 때 어떤 동작이 이루어져야 할까요?
- 복사를 금지합니다. (Uncopyable 클래스 상속, =delete 활용)
- 관리하고 있는 자원에 대해 참조 카운팅을 수행합니다. (custom deleter와 함께 std::shared_ptr를 활용한다.)
- 관리하고 있는 자원을 진짜로 복사합니다.
- 관리하고 있는 자원의 소유권을 옮깁니다. (std::unique_ptr 활용)

[ 이것만은 잊지 말자! ]
- RAII 객체의 복사는 그 객체가 관리하는 자원의 복사 문제를 안고 가기 때문에, 그 자원을 어덯게 복사하느냐에 따라 RAII 객체의 복사 동작이 결정됩니다.
- RAII 클래스에 구현하는 일반적인 복사 동작은 복사를 금지하거나 참조 카운팅을 해 주는 선으로 마무리하는 것입니다. 하지만 이 외의 방법들도 가능하니 참고해 둡시다.

항목 15 : 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자.

class RAIIObject {
public:
    ...
    // Case 1, Using "Explicit type conversion"
    Resource get() const { return res; }
    // Case 2, Using "Implicit type conversion"
    operator Resource() const { return res; }
private:
    Resource res;
};

int main() {
    ...
    RAIIObject objec(res);
    Resource res1 = obj.get();  // Case 1
    Resource res2 = obj;        // Case 2
    return 0;
}

[ 이것만은 잊지 말자! ]
- 실제 자원을 직접 접근해야 하는 기존 API들도 많기 때문에, RAII 클래스를 만들 때는 그 클래스가 관리하는 자원을 얻을 수 있는 방법을 열어 주어야 합니다.
- 자원 접근은 명시적 변환 혹은 암시적 변환을 통해 가능합니다. 안전성만 따지면 명시적 변환이 대체적으로 더 낫지만, 고객 편의성을 놓고 보면 암시적 변환이 괜찮습니다.

항목 16 : new 및 delete를 사용할 때는 형태를 반드시 맞추자.

[ 이것만은 잊지 말자! ]
- new 표현식에 []를 썼으면, 대응되는 delete 표현식에도 []를 써야 합니다. 마찬가지로 new 표현식에 []를 안 썼으면, 대응되는 delete 표현식에도 []를 쓰지 말아야 합니다.

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

// Function parameter pass by value involves copy initialization, not direct initialization.
class Example {
public:
    Example() {}
    explicit Example(const Example& ex) {}
    Example& operator=(const Example& ex) {}
};
void test(Example ex) {}
int main() {
    Example ex;
    test(Example());  // compile error! // means "Example test(Example)::ex = Example();"
    test(ex);         // compile error! // means "Example test(Example)::ex = main()::ex;"
    return 0;
}

함수 매개변수로 pass by value를 할 때는 direct-initialization이 아니라 copy-initialization으로 인식되게 된다. 따라서 위와 같이 compile error가 뜨게 되는 것이다.
http://stackoverflow.com/questions/4153527/explicit-copy-constructor-behavior-and-pratical-uses

int func();
void process(std::shared_ptr<Example> ex, int n);

int main() {
    // Case 1 : Compile Error!
    // std::shared_ptr의 constructor가 explicit으로 선언되었기 때문! (위 예제 참조)
    process(new Example, func());

    // Case 2 : Exception Unsafe!
    // new Example과 func()중 어떤걸 먼저 실행할 지 알 수 없다! 
    // (C++에서는 java나 C#등과 다르게 이 부분에 대해 자유도가 많다.)
    // 만약 실행 순서가 new Example -> func() -> std::shared_ptr<Example>() 라면,
    // func()에서 예외발생시 memory leak이 발생한다. 따라서 Exception Unsafe하다!
    process(std::shared_ptr<Example>(new Example), func());

    // Case 3 : Exception Safe!
    std::shared_ptr<Example> ex(new Example);
    process(ex, func());

    // Case 4 : Exception Safe and one line code!
    process(std::make_shared<Example>(), func());

    return 0;
}

[ 이것만은 잊지 말자! ]
- new로 생성한 객체를 스마트 포인터로 넣는 코드는 별도의 한 문장으로 만듭시다. 이것이 안 되어 있으면, 예외가 발생될 때 디버깅하기 힘든 자원 누출이 초래될 수 있습니다.



학교도서관에서 effective c++, more effective C++을 빌린지가 벌써 3주가 다 되어갑니다. 연장신청도 4주까지가 한계입니다. 앞으로 8일 남았는데 하루에 챕터 2개씩 포스팅하는걸 목표로 하도록 하겠습니다. 그러면 충분히 effective, more effective C++를 다 포스팅하고 책을 반납할 수 있겠군요! 그럼 내일 뵈요~~

댓글