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

  • 힙에 생기지 않는 자원은 스마트 포인터로 처리해 주기엔 맞지 않다.

  • RAII 법칙(생성시 자원 획득, 소멸시 자원 해제)을 따르는 클래스의 복사될 때 동작법
    1. 복사를 금지한다 : 복사되면 안되는 RAII 클래스는 반드시 복사 함수 막을 것.
    2. 관리하고 있는 자원에 대해 참조 카운팅 수행 : shared_ptr을 데이터 멤버로 넣는다.
      • shared_ptr은 기본적으로 참조 카운트가 0이 되면 대상을 삭제하지만 두번째 매개변수로 삭제자 지정을 할 수 있다.(auto_ptr은 없음)
      • shared_ptr이 삭제를 맡으므로 소멸자는 선언하지 않아도 됨.
    3. 관리하고 있는 자원을 진짜로 복사한다 : 깊은 복사를 수행할 것
    4. 관리하고 있는 자원의 소유권을 옮긴다 : auto_ptr의 복사 동작
  • 객체 복사 함수는 컴파일러에 의해 생성될 여지가 있으므로 필요시 직접 만들 수밖에 없다.

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

  • RAII 클래스의 객체를 그 객체가 감싸고 있는 실제 자원으로 변환해야 할 때가 존재.
    1. 명시적 변환 : get()같은 멤버 함수 제공. 사용자가 하부 수준 API를 쓰고 싶을 때마다 get을 호출해야 함.
    2. 암시적 변환 : operator()를 사용. 자원관리 클래스를 쓰려 한 부분에서 원하지 않는 타입 변환이 일어날 염려가 있다.
  • 제대로 만들어진 스마트 포인터 클래스는 거의 모두 포인터 역참조 연산자(operator->, operator*)도 오버로딩하고 있다.

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

  • new 연산자를 통해 표현식을 꾸미게 되면
    1. 메모리 할당(operator new 함수가 쓰인다)
    2. 할당된 메모리에 대해 한 개 이상의 생성자 호출
  • delete 표현식을 쓸 경우
    1. 할당된 메모리에 대해 한 개 이상의 소멸자 호출
    2. 메모리 해제(operator delete 함수가 쓰인다)
  • 단일 객체의 메모리 배치 구조(layout)는 객체 배열에 대한 메모리 배치 구조와 다르다.
    • 한 개의 객체 : [ object ]
    • 객체의 배열 : [ n ][ object ][ object ][.. »- 배열의 크기 정보가 있다.
  • new 표현식에 []를 썼으면 delete 표현식에도 []를 써야 한다!

  • 동적 할당 메모리에 대한 포인터를 멤버 데이터로 가지는 클래스의 생성자가 여러개일 경우 생성자의 new를 모두 같게 맞춰야 소멸자의 delete와 쌍을 맞출 수 있다.

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

  • C++ 컴파일러는 다른 언어(C# 또는 자바)와 달리 매개변수의 평가 순서가 자유롭다.
  • 컴파일러마다 매개 변수 평가 순서가 다를 수 있다.
  • 문제가 발생할 수 있는 예시
    • ex)
      processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority(1)); 
      
  • 순서 예 1.
    1. priority 호출
    2. new Widget 실행
    3. tr1::shared_ptr 생성자 호출
  • 순서 예 2.
    1. new Widget 실행
    2. priority 호출 -> 여기서 예외 발생시 Widget 자원 누수
    3. tr1::shared_ptr 생성자 호출
  • new로 생성한 객체를 스마트 포인터로 넣는 코드를 별도의 한 문장으로 만들자