항목 27 : 힙에만 생성되거나 힙에는 만들어지지 않는 특수한 클래스를 만드는 방법

  • 객체의 생성 장소를 힙으로 한정하는 방법
    1. 암시적 객체 생성/소멸을 불법화
      • 소멸자만 private로 하고 유사 소멸자를 제공한다. »- 상속 문제 : 소멸자를 protected로 한다. »- 다른 클래스 내부 변수로 포함 : 객체 대신 포인터를 포함시킨다.
  • 어떤 객체가 힙에 생성되었는지 그렇지 않은지를 알아내는 방법
    1. 생성자로 힙인지 아닌지 구별할 방법은 없다.
      • operator new를 재정의한다?(플래그 사용)
        1. 배열일 때 첫 번째 생성자만 호출된다.
        2. operator new를 중복하여 사용할 시에 operator new 후에 생성자가 호출되고 다음 operator new가 호출됨을 상정할 수가 없다. ex) new A(*new A)
      • 주소 공간을 연속된 주소로 할당(스택은 아래로 힙은 위로)한다는 사실을 활용한다면?
      • 이식성을 무시한 방식
      • 스택 변수를 임시로 만들고 그 스택변수의 주소보다 크면 스택, 아니면 힙에 만들어졌다고 판단.
      • 근데 정적 객체를 고려하지 않아서 문제이다. : 대개 힙 영역 아래에 두므로 힙과 static 객체 영역 구분이 되지 않는다.
  • 힙에 있는 주소라고 100% 안전하게 삭제할 수 있지는 않다.
  • ex)
    class Asset 
    { 
    private: 
    UPNumber value; 
    }; 
    Asset *pa = new Asset;
    
  • 여기서 pa->value는 힙에 있으나 delete를 하면 안된다.

  • 어떤 포인터가 힙 주소를 가리키는지 알아내는 것보다 어떤 포인터가 삭제해도 괜찮은 포인터인지 알아내는 것이 더 쉽다.
    • operator new에서 지금까지 할당한 주소들의 콜렉션을 유지한다.
    • ex)
      void *operator new(size_t size) 
      { 
          void *p = getMemory(size); // 메모리를 할당하고 가용메모리가 없는 상황 처리 함수 호출 
          // p를 할당된 주소 컬렉션에 추가 
          return p; 
      } 
      void operator delete(void *ptr) 
      { 
          releaseMemory(ptr); // 관리장치에게 현재 메모리를 돌려줌 
          // 할당된 주소 콜렉션에서 ptr 제거 
      } 
      
    • 세 가지 걸림돌이 존재한다.
      1. 전역 함수로 객체지향과 약간 거리가 있다. 
          - 미리 정의된 동작이 있으니 보통의 시그니쳐를 숨기게 되므로 껄끄럽다. 
          - 전역 함수를 똑같이 구현한 다른 소프트웨어와 잘 작동하지 않는다. 
      2. 매번 할당된 주소의 정보를 갱신하고 유지하는 오버헤드가 있다. 
      3. isSafeToDelete 함수가 모든 경우에 대해 동작하도록 구현하기가 불가능하다. 
          - 다중상속 / 가상 기본 클래스로부터 만들어진 객체는 주소도 여러개이다. 
    
  • 앞의 걸림돌을 피하고 함수가 제공하는 기능을 그대로 쓰자!
    • 추상 믹스인 기본 클래스 : 명확한 기능을 딱 하나만 제공하는 클래스.
    • 이 클래스의 파생 클래스가 제공할지도 모르는 다른 기능과 호환되도록 설계된 희한한 클래스
    • 거의 항상 추상 클래스로 만들어진다.
    • 추상 클래스에 operator를 오버라이드 하고 isOnHeap도 만들고 이를 상속시킨다.
    • 3번 문제는 모든 클래스에 대해 준비할 필요가 없으므로(하나의 타입에 대해서만 책임지면 되므로)
      const void *rawAddress = dynamic_cast<const void*>(this)
      
    • 식으로 현재 객체가 자리잡은 메모리의 시작 위치를 가리키는 포인터를 얻어올 수 있다.
    • 믹스인 클래스의 단점 : 기본 제공 타입에 적용할 수 없다.
  • 객체가 힙에 생성되지 않게 하기
    • 세 가지 고려점이 존재
      1. 객체가 직접 인스턴스화 하는 경우 
      2. 파생 클래스 객체의 기본 클래스 부분으로 인스턴스화 되는 경우 
      3. 다른 객체의 멤버로 들어가는 경우 
    
    1. : operator new를 private로 선언
      • operator delete도 따로 둬야 할 이유가 없으면 new와 함께 두자.
      • 배열을 위한 operator new[] delete[]도 함께 두자
    2. : 1번이 해결되며 자연스럽게 해결
      • operator new, operator delete가 상속이 가능한 함수라 기본 클래스의 private 버전이 그대로 내려옴.
      • 파생 클래스에서 operator new / operator delete를 또 선언하면 호출되므로 다른 방안이 필요하다.
    3. : 객체를 멤버로 가지는 다른 객체의 할당 방식에까지 영향을 미치지 않는다.
  • 힙에 있을 때 예외를 일으키는 건? -> 결국 이식성 있는 방법은 없다.