본문 바로가기

기타

C++에서 스마트 포인터 템플릿

C++에서 스마트 포인터 템플릿


데이비드 하비 (David Harvey)

ⓒ 1995/1996 David Harvey. All rights reserved 
Contact 
Home: http://www.davethehat.com/ 
작성 날짜 :1997년 6월 27일

1997 년 11 월 30 일 수정


이 글을 쓸 수 있도록 권한을개체 디자이너에 대한 감사를 표함

이 문서 형식화 된 개체 변수의 구현에서 작동하도록 넣어 유용한 C + + 템플릿 관용구 설명을 제공합니다.


템플릿 의 C + + 몇 시간 동안 , 작년 정도 의 언어 의 이 기능 의 잠재력 에 대한 관심이 급증 가능성을 보여줬습니다 . 일부가되어 있지만 이 인기있는 컴파일러 의 믿을 수있는 (물론, 합리적으로 신뢰할 수있는 ) 구현 의 가용성 향상 때문이기도 하고, 일부ANSI / ISO C 에영향 + Stepanov 리 의 표준 템플릿 라이브러리 + 표준 초안 . 때문에 대한 제안 된 표준 라이브러리 에서 템플릿광범위한 사용 의 C + + , 우리는 그들 모두 를 사용하여 훨씬 더 익숙해 질 것입니다. 도서관의 관용구 는 모방 과 강화 를 생성 하고, 나는 몇 년 에 우리는C + + 커뮤니티에서 상속 보다 템플릿 얘기를 더 많은 시간 을 소비 할 것으로 예측 .

현재 의 템플릿 클래스 의 가장 잘 사용하는 C + + 매개 변수화 , 형식 안전 컬렉션을 제공하는 것입니다. 이 관용구 는 그의 도구 템플릿을 지원하지 않는 컴파일러 벤더 가 제공하는 번거로운 상속 기반 또는 포인터 - 투 - 빈 컬렉션 클래스 를 통해 분명히 장점이 있습니다. 이 문서의 목적은 , 그러나, 우리가 그 클래스 의 멤버에 대한 액세스 를 유지 , 다른 클래스 의 인스턴스 를 래핑 할 수있는 클래스를 제공 하지만, 래핑 된 클래스에 투명한 방식으로 기능을 추가 , 다른 템플릿 관용구 를 탐험 하는 것입니다. 역 참조 연산자를 오버로드 - 를 사용 , 수명 및 소유권 에게 제어 를 유지하면서 >템플릿 클래스 를 위해, 우리는 ,포함 클래스 포인터로템플릿 클래스 의 특수화 의 인스턴스를 사용할 수 있습니다. 우리는 형식화 된 개체 참조 variables.These 의구현을 계속하기 전에 ,간단한 참조 카운팅 메커니즘을 구축을 먼저 살펴 보겠습니다 , ​​해당 개체 의 참조 및 수명을 관리하는 그들이 더 이상 참조 되지 않은 경우 자동으로 삭제됩니다.

이 관용구 의 핵심은 역 참조 연산자의 오버로드 입니다 . 템플릿 클래스에서클래스의 인스턴스를 배치 하고,역 참조 연산자 에서 래핑 된 인스턴스의 주소를 반환하여 , 우리는 여전히 래핑 클래스의 멤버 에 액세스 할 수 있습니다 :

  template <class T> 
  class Wrapper { 
  public: 
          T* operator->() { return &myT; } 
  private: 
          T myT; 
  }; 

  int main() { 
          Wrapper<Thing> wThing; 
          wThing->Foo(); // calls Thing::Foo() 
          ... 
  } 

C + +역 참조 연산자에 대한 특별 대우 를 정의합니다. 개체에 적용 할 때 이멤버 ( 변수 나 함수 ) 이름이 와야합니다. 피연산자는 효과적으로 결과 에 의해식에 대체됩니다. 연산자 는 오버로드 역 참조 연산자 , 이쪽도 적용된다 , 그래서포인터 가 반환 될 때까지 공급하는 객체 나 참조를 반환합니다. 멤버의 이름은 다음 요청 된 멤버 함수 를 호출하거나 필요한 회원 데이터에 액세스 하기 위해이 에 사용됩니다. 이 과정 에서 결과를최종 클래스는 , 지정된 이름의 멤버가 없는 경우 , 컴파일러는오류 메시지를 표시합니다.

그것이 의미하는 바와 같이 , 이 예 는 우리 클래스에 아무것도 추가하지 않습니다 : 그것은 단순히 전달 의 포함 된 인스턴스로 호출합니다. 진행 하기 전에 , 가정 은 분명히 템플릿 매개 변수 클래스 T. 위해 만들어지고있는 것을 고려 , 이 템플릿 에 의해 래핑 하는 클래스는기본 생성자가 있어야합니다. 그렇지 않으면 우리는클래스템플릿을 인스턴스화 할 때 ,컴파일러는 불평 합니다 . 이것은 항상흥미로운 클래스가기본 생성자가 있는 경우 가 아니므로 , 다소 우리를 제한하는 나타납니다. 우리는 확실히 가능한 각 매개 변수 클래스에 대한 생성자를 사용하여 우리의 템플릿 클래스 를 제공 하지 않습니다.

문제를 해결하는 데는 두 가지 방법 이 있습니다. 첫째로 우리는인수로 매개 변수 class.Here 의 인스턴스에const 참조 는 우리의 새로운 템플릿 클래스 소요되는 템플릿 클래스 를위한생성자를 정의 할 수 있습니다 :

 

template <class T>
class Wrapper1 {
public:
        Wrapper1(const T& rT) : myT(rT) {}
        T* operator->()                 { return &myT; }
private:
        T myT;
};

int main() {
        Wrapper1<Thing> wThing(Thing(10,20));
        ...
}

 

 

이렇게하면 그냥 초기화의 목적을 위해 만든 임시 개체 포함 된 인스턴스를 초기화 할 수 있습니다. 작동하지만 비효율적 입니다 : 우리는 매개 변수 클래스의 두 인스턴스를 하나의 단지를 만들었습니다. 또한, 회전 관용구 컴파일러에 의해 생성 또는 클래스 구현에 의해 정의 된 복사 생성자을 가지는 매개 변수 클래스에 의존합니다. 이러한 클래스이 템플릿에 의해 래핑 할 수 없습니다 클래스는 복사 생성자 개인 선언하여 인스턴스 복사를 방지하기 위해 선택했을 수 있습니다.플러스 측면에서, 우리는 포장 클래스의 이미 기존 인스턴스에서 템플릿 클래스 초기화 할 수 있다는 장점이 있습니다. 그것은 목적을 위해 만든 임시 개체가 될 필요가 없습니다.

두 번째 대안은 오히려 전체 인스턴스를보다 템플릿 클래스 래핑 된 클래스에 대한 포인터를 저장하는 것입니다 :

  template <class T> 
  class Wrapper2 { 
  public: 
          Wrapper2(T* pT) : my_pT(pT) {} 
          T* operator->()             { return my_pT; } 
  private: 
          T* my_pT; 
  }; 

  int main() { 
          Thing* pThing = new Thing(10,20); 
          Wrapper2<Thing> wThing(pThing); 
          ... 
          delete pThing; 
  // DANGER: wThing now invalid! 
  } 

이제 래핑 클래스의 인스턴스가 하나만 만들어집니다. 우리는 기존 인스턴스를 래핑 할 수있는의 이전 버전에서 우위를 유지하고, 복사 생성자에 액세스 할 수없는 클래스를 래핑하는 템플릿 클래스를 사용할 수 있습니다. 하지만 지금 우리는 우리가 템플릿 생성자에 전달하여 포인터를 관리해야합니다. 우리가 원하는 모두 삭제하는 것을, 정의 일생에 단일 인스턴스의 래퍼 인 경우 인스턴스는 개별적으로 복잡하고 잠재적으로 오류가 발생하기 쉽습니다. 우리는 템플릿 클래스 생성자 호출에서 임시 포인터로 새 인스턴스를 만들려고하고, 템플릿 소멸자에서 포인터 자체를 삭제해야 할 수도 있습니다 :

 

template <class T>
class Wrapper3 {
public:
        Wrapper3(T* pT) : my_pT(pT) {}
        ~Wrapper3()                 { delete my_pT; }
        T* operator->()             { return my_pT; }
private:
        T* my_pT;
};

int main() {
        Wrapper3<Thing> wThing(new Thing(10,20));
        ...
}

 

포인터의 소유권이 우리가 이미 기존 인스턴스에 대한 포인터를 전달하면, 그래서 우리는 그것을 삭제하지 않도록주의해야 템플릿 클래스 인스턴스에 의해 유지된다는 것을 의미합니다. 더욱이, 우리는 래핑 된 클래스의 스택이나 정적 인스턴스의 주소를 전달하지 있었다 :

  ... 
  Thing danger(5,10) 
  Wrapper3<Thing> wThing(&danger); 
  ... 

wThing가 범위를 벗어나 소멸자가 호출되면 시도는 스택에 개최 메모리를 삭제하려고합니다.스택에 인접한 데이터가 손상되므로 항상 나쁜 소식입니다. 이 솔루션을 사용하려면 약간의 관심과 훈련이 필요하지만, 우리는 유용한 전술이 될 수 있음을 볼 수 있습니다.

종종, 우리는 인스턴스에 대한 참조 수를 기준으로 객체를 관리해야합니다. 우리는 우리가 이런 방식으로 사용할 수 있도록하려는 클래스 래퍼를 제공하는이 템플릿 관용구를 사용할 수 있습니다. 일을 간단하게하기 위해, 계산 클래스는 참조를 수동으로 증가와 감소 기능을 제공합니다 : 우리는 나중에 자동화하는 방법 것입니다. 다음은 클래스입니다 : 지금, 우리는 기본 생성자 매개 변수 클래스에 사용할 수 있다고 가정합니다.

 

  template <class T> 
  class Counted { 
  public: 
          Counted() : Count(0) {} 
          ~Counted()           { ASSERT(Count == 0); } 

          unsigned GetRef()    { return ++Count; } 
          unsigned FreeRef()   { ASSERT(Count > 0); return --Count; } 
          T* operator->()      { return &myT; } 
  private: 
          T               myT; 
          unsigned        Count; 
  }; 

참조 삭제 개체에 대해 존재하지 않는 검사를 주장하며 count가 0보다 큰 경우의 수를 감소시키는 것은에만 수행됩니다.참조 카운트를 업데이트 회원은 횟수의 새 값을 반환합니다.

템플릿 자체에 정의 된 멤버 함수 사이의 불일치, 우리가 템플릿 클래스의 인스턴스를 operator.Given 오버로드 역 참조 미덕으로 감싸 인스턴스에서 액세스 할 수있는 사람, 우리는 무엇에 따라 과 화살표 표기법을 혼합한다 호출되는 :

  Counted<Thing>  t; 
  t.IncRef(); // Call Template<Thing>::IncRef() 
  ... 
  t->Foo(); // Call Thing::Foo() 
  ... 
  t.DecRef(); 

오버로드 역 참조 연산자는 객체가 아닌 포인터 적용되기 때문에 또 다른 단점 발생한다. 우리는어진 <Thing>에 대한 포인터 주위에 전달하기로 결정하는 경우, 첫 번째 역 참조 포인터를 필요가 래핑 된 인스턴스로 호출합니다. 불편합니다.

  void TryIt(Counted<Thing>* p) { 
          p->IncRef(); // Call Template<Thing>::IncRef() 
          (*p)->Foo(); // Call Thing::Foo() 
          p->Foo(); // NB Won't work! 
          p->DecRef(); 
  } 

그러나, 우리는 자동으로 참조 카운팅 및 개체 삭제를 관리하는 두 번째 템플릿 클래스 뒤에 이것의 다량 을 숨길 수 있습니다. 변수가 효과적으로 항상 스택 이나 정적 에 어떤 종류 의참조 ( C + + 관점에서 ,참조 또는 포인터 ), 그리고 결코' 전체 ' 객체 하는 스몰 토크 와 유사한 언어 에서 개체 변수 처럼 이 클래스 행위 의 인스턴스 저장 . 방법은 개체에 대한 이러한 제한 은 생성 하고 사용할 수 를 통해 장점을 가지고 C + 변수를 하나의 개체 특히 , 할당 에서 C. 와 완벽한 호환성 + 의 위치를 다른 하나는 단순히 참조 되지객체의 복사본을 공유하는 방법 : 복사 생성자 의 합병증 대입 연산자 는 무시할 수 있습니다. 최근 언어 중 이 모델 을 채택 하기 보다는 ' 선언 어디서나 ' 사용이합리적인 C + + 휴렛 팩커드 에서 볼랜드의 델파이 , 자바 입니다.

우리는 생성자 에 전달 된포인터의 초기화 ,파라미터 클래스의 임베디드 인스턴스에 대한 포인터를 사용하여 참조 카운트 템플릿 의 변형 으로 시작합니다. 생성자의 본문에서주장은 잘못된 초기화 에 대한 몇 가지 보호 기능 을 제공합니다.

  template <class T> class Objvar; 

  template <class T> 
  class Counted 
  { 
          friend class ObjVar<T>; 
  private: 
          Counted(T* pT) : Count(0), my_pT(pT) 
                              { ASSERT(pT != 0); } 
          ~Counted()         { ASSERT(Count == 0); delete my_pT; } 

          unsigned GetRef()  { return ++Count; } 
          unsigned FreeRef() { ASSERT(Count!=0); return --Count; } 

          T* const my_pT; 
          unsigned Count; 
  }; 

이제 이 클래스가 될 것을 효과적으로 ,헬퍼 클래스는 , 모든 구성원이 개인 이며, 우정 은 마지막으로 개체 변수 자체를 구현할클래스에 부여됩니다. 이 보호 는 가 objVar 의 선언 의 개인 섹션에서 계수 된 템플릿 클래스 를 선언 하여 관리 할 수 있습니다. 그러나 현재의 컴파일러는 중첩 된 템플릿 클래스 에 대처하는 능력 에 차이 : 친구 를 사용하여 솔루션 도가 objVar 클래스 자체 의 선언을 단순화장점이 있습니다.

어진 클래스가 더 이상역 참조 연산자를 오버로드 하지 않습니다. 해당 가 objVar 클래스에friend 선언 은 실제 객체에 대한 포인터를 효율적으로 검색 할 수 있다는 것을 의미합니다. 또는 우리는포장 인스턴스에대한 포인터를 반환하는 인라인 접근 멤버 함수를 제공 할 수 있습니다.

우리는 지금 이 계산 클래스의인스턴스 에 대한 포인터를 래핑하는객체 변수 클래스를 정의 할 수 있습니다. 우리는 임무를 오버로드 와복사 생성자 를 제공 하여 참조 의 공유를 관리 할 수 ​​있습니다. 여기에클래스의 선언 은 다음과 같습니다

  template <class T> 
  class ObjVar 
  { 
  public: 
          ObjVar(); 
          ObjVar(T* pT); 
          ~ObjVar(); 
          ObjVar(const ObjVar<T>& rVar); 

          ObjVar<T>& operator=(const ObjVar<T>& rVar); 

                  T* operator->(); 
          const T* operator->() const; 

          friend bool operator==(const ObjVar<T>& lhs, 
                          const ObjVar<T>& rhs); 

          bool Null() const {return m_pCounted == 0}; 
          void SetNull() { UnBind(); } 

  private: 
          void UnBind(); 
          Counted<T>* m_pCounted; 

  }; 

실제로, 나는 모든 기능 성능상의 이유로 인라인 될 것으로 기대된다.각 구성원 다음 논의는 쉽게 참조 - 라인을 그들에게 제시한다.

기본 생성자는 효과 NUL 객체 만들고, 계산 객체에 대한 포인터가 0으로 설정되어있는가 objVar을 만듭니다.멤버 함수 Null이 ()이 테스트하기 위해 제공되며, setNull를 () 함수는 현재 개체에 바인딩을 잃게하는 것입니다.

하나의 인수 생성자는 증가 , (새로운 사용)이 객체의 참조 카운트를 통과 포인터로 계산 클래스의 인스턴스를 만듭니다.소멸자 바인딩을 해제합니다 (아래에 설명)를 호출 :

  template<class T> 
  ObjVar<T>::ObjVar() 
  : m_pCounted(0) {} 

  template<class T> 
  ObjVar<T>::ObjVar(T* pT) 
  { 
          m_pCounted = new Counted<T>(pT); 
          m_pCounted->GetRef(); 
  } 

  template<class T> 
  ObjVar<T>::~ObjVar() 
  { 
          UnBind(); 
  } 

가 objVar 인스턴스가 래핑 된 계산 인스턴스에 대한 참조를 잃을 때마다 멤버 함수 바인딩 해제가 호출됩니다. 그 과정에서 참조 횟수가 0이되면 래핑 된 인스턴스는 안전하게 삭제할 수 있습니다.계산 인스턴스가 objVar 포인터 현재의 변수가 현재 참조 빵점 객체를 나타 내기 위해 0으로 설정됩니다.

  template<class T> 
  void ObjVar<T>::UnBind() 
  { 
          if (!Null() && m_pCounted->FreeRef() == 0) 
                         delete m_pCounted; 
          m_pCounted = 0; 
  } 

오버로드 역 참조 연산자는 단순히 현재 참조하는 계산 인스턴스에서 개최 된 개체를 반환합니다.시도가 null 객체 변수에 연산자를 적용하려 할 경우, 우리는 예외를 throw합니다.

  template<class T> 
  T* ObjVar<T>::operator->() 
  { 
          if (Null()) 
                  throw NulRefException(); 
          return m_pCounted->my_pT; 
  } 

  template<class T> 
  const T* ObjVar<T>::operator->() const 
  { 
          if (Null()) 
                  throw NulRefException(); 
          return m_pCounted->my_pT; 
  } 

이 연산자의 두 가지 버전이 제공 비 const (래핑 된 클래스에 대한 포인터를 반환), 다른 상수 (상수 T에 대한 포인터를 반환). 있습니다 이것은 우리가 그것을 포장되는 오브젝트를 변경하는 것은 불가능하다 통해, 일정한 개체 변수를 선언하고 사용할 수 있습니다. 상수가 objVar에 의해 참조되는 개체를 변경할 수 없습니다 것을 의미하지 않습니다. 마찬가지로 원시 C + + 포인터와 참조, 다른 객체 동일한 기본 객체에 비 const가 objVar 보유 할 수 있습니다.

물건 복사 생성자와 대입 연산자에서 더 복잡. 복사 할당 객체를 복사하지 않고, 참조를 공유하는 의미 것을 기억하십시오.복사 생성자를 들어, 우리는 인수 생성자에 계산 객체에 대한 포인터를 저장하고 해당 참조 횟수를 증가시켜야합니다 :

  template<class T> 
  ObjVar<T>::ObjVar(const ObjVar<T>& rVar) 
  { 
          m_pCounted = rVar.m_pCounted; 
          if (!Null()) 
                  m_pCounted->GetRef(); 
  } 

대입 연산자에서, 우리는 현재 계산 객체를 분리 참조 제로의 경우, 인수에서 계산 개체를 연결 계산하고 삭제 감소, 그 참조 카운트를 증가해야합니다. 언제나처럼, 우리는 자기 임무와 올바르게 처리하도록주의해야합니다.문제는 현재 (왼쪽) 개체 수를 감소시키는하기 전에 인수 (오른쪽) 참조 카운트를 증가하여 깔끔하게 여기에서 다루어진다. 자기 과제의 경우,이 참조가 변경 계산하고 결과적으로 계산 인스턴스의 삭제를 방지 떠날 것입니다.

  template<class T> 
  ObjVar<T>& operator=(const ObjVar<T>& rVar) 
  { 
          if (!rVar.Null()) 
                  rVar.m_pCounted->GetRef(); 
          UnBind(); 
          m_pCounted = rVar.m_pCounted; 
          return *this; 
  } 

마지막으로, 우리는 평등을 선언하고 연산자 같음 없습니다. 두 개체 변수들은 동일한 기본 인스턴스를 래핑하는 경우에만 동일합니다. 계산 클래스 내에서 래핑 된 개체의 주소를 비교하여 개체 ID에 대한 테스트를 구현합니다. 이것은 우리가 개체 변수에 대해 원하는 정확히​​이지만, 어떤 경우 같음 연산자 동치 관계 (예를 들어, 인스턴스 STL 컨테이너 클래스)로 이해되는 상황에서 문제가 발생할 수 있습니다. 이 경우에, 당신은 어떤지를 판정하기 전에 역 참조 포인터를하는 것이 좋습니다.

  template<class T> 
  bool operator==(const ObjVar<T>& lhs, 
                          const ObjVar<T>& rhs) 
  { 
          return lhs.m_pCounted->my_pT 
                          == rhs.m_pCounted->my_pT; 
  // or *(lhs.m_pCounted->my_pT) == *(rhs.....) 
  } 

  template<class T> 
  bool operator!=(const ObjVar<T>& lhs, 
                          const ObjVar<T>& rhs) 
  { 
          return !(lhs == rhs); 
  } 

단순히 평등 에 대한 테스트 의 결과를 부정 으로비항 등 연산자 가가 objVar 클래스의 친구로 정의 될 필요 하지 않습니다.

조금 독창성 으로, 그것은중간 참조 카운트 클래스 없이 개체 변수 를 구현 하는 것도 가능합니다. 이 포함 된 개체에 액세스 할 수 있는 간접 레벨을 절약 할 수 있지만, 그것은 클래스 에 추가 포인터의 오버 헤드를 소개합니다. 이 포인터 는레퍼런스 카운트 를 잡고정수를 참조하는 데 사용됩니다 , 이것은 이래핑 된 개체 에 대한 모든 참조 사이에서 공유 할 수 있다는 것을 의미합니다. 그러나, 우리의 개체 변수 인스턴스 중 하나 에서 개최 된 데이터 만참조 카운트 개체에 대한 하나의 포인터 , 전달하고 개체 변수 인스턴스 를 반환 하는 것은 효율적입니다.

이 개체 변수 는 사용하기 간단 하고, 여러 곳 의 개체에 일반 포인터 또는 참조 의 장소를 가지고 갈 수있다. 자, 우리는컴퓨터 사용자 가 가상 세계를 탐험 할 수있는 시뮬레이션을 구축 하고 가정합니다. 사용자가 현재 위치 에서 개체를 다른 사용자와 의 상호 작용 , 가상 위치의 숫자를 통해 탐색 할 수 있습니다. 이 건물을 설계 하거나대화 형 게임시스템의 일부가 될 수 있습니다. 클래스 에이전트는 사용자를 대표하고, 항상 현재 위치 와 연관됩니다. 에이전트 의인스턴스는초기 위치에 생성 되므로에이전트 생성자는 위치 클래스가 objVar 걸립니다 .

  class Agent { 
  public: 
          Agent(ObjVar<Location> InitialLoc) 
                  : CurrentLoc(InitialLoc) {} 
          void Enter(ObjVar<Location> NewLoc) 
                  { CurrentLoc = NewLoc; } 
          ObjVar<Location> GetCurrentLoc 
                  { return CurrentLoc; } 
  private 
          ObjVar<Location> CurrentLoc; 
  }; 

전체 개체를 전달 하고 이러한 에이전트 멤버 함수 에서 할당 되는 것처럼 보이는 반면 , 잊지 마세요 , 위치 의 실제 인스턴스를 포인터로 개최가 objVar 템플릿에 래핑됩니다 . 우리는 널 포인터 가 전달되거나 할당 된 것에 대해 걱정할 필요없이 변수를 직접 사용 의 편익을 얻는다.

위치 에 대한요구 사항은 다소 복잡하다. 위치 는 현재 존재하는 모든 에이전트 를 추적 해야합니다. 이들은 일종의 목록 에 보관 해야합니다 , 그리고자연 솔루션은 일반적으로 해당 객체에 대한 포인터템플릿 목록 클래스를 인스턴스화 하는 것입니다. 목록 의 개체에 포인터를 두는 것은 소유권 문제가 발생할 수 있습니다 : 이러한 객체 는이 객체를 삭제하는 책임이 하나 이상의 목록 에 포함되어 있다면? ObjVars 의 목록문제는 명확하다 : 한 번 가 objVar 이가 objVar 자체가 자동으로 삭제됩니다 같은목록에서 제거됩니다 :래핑 된 개체 의 참조 카운트가 결과적으로 제로 자체가 회수개체 됩니다 경우에만 사용할 수 있습니다. 여기에 ,리스트 템플릿 클래스 에서 위치 의 구현의 일부, 표준 목록 접근 된다고 가정 :

  class Location { 
          public: 
                  void AgentEnters(ObjVar<Agent> A) 
                          { Agents.Add(A); } 
                  void AgentLeaves(ObjVar<Agent> A); 
                          { Agents.Remove(A); } 
          private: 
                  List<ObjVar<Agent> > Agents; 
  }; 

우리는 어떻게 추가합니까 이 세상에서 에이전트를 제거? 우리는 전체 세계를 대표하는 객체의 책임 수 있습니다. 월드 클래스의 선언의 일부이다

  class World { 
          public: 
                  ... 
                  ObjVar<Agent> CreateAgent(); 
                  void KillAgent(ObjVar<Agent> A); 
                  ... 
          private: 
                  List<ObjVar<Location> > Locations; 
                  List<ObjVar<Agent> > Agents; 
                  ObjVar<Location> InitialLoc; 
  }; 

에이전트를 만들기 새 에이전트 초기화 에이전트 개체 변수 형식의 인스턴스를 선언하고 에이전트의 세계 목록에 그녀를 추가 포함됩니다.세계는 에이전트를 작성할 초기 위치를 기억해야합니다.

  ObjVar<Agent> World::CreateAgent() 
  { 
  // NB dot syntax! 
          ASSERT(!InitialLoc.Null()); 

          ObjVar<Agent> A(new Agent(InitialLoc)); 
          InitialLoc->AgentEnters(A); 

          Agents.Add(A); 
          return A; 
  } 

에이전트를 제거하는 것만으로 간단하지만, 가시성 문제를 발생시킵니다. 그것은 그의 여행에서 에이전트가 다른 객체와의 관계 체결했다고 할 수 있습니다. 이상적으로, 우리는 몇 가지 수준에서보기 에이전트의 관점에서 각각의 연결을 관리하여 다루고 있습니다. 알림 메커니즘 사용하여 수행 할 수도 있고, 이러한 연결 을 기억 개별 에이전트를 포함 할 수 있습니다. 반대로 우리는 더 이상 참조가 될 때까지 에이전트 객체의 실제 삭제를 연기 할 개체 변수 자체에 의존하고 있습니다. 에이전트의 액세스 확인하실 수 있습니다 '좀비'상태를 입력해야하는 것을 의미한다.

  void World::RemoveAgent(ObjVar<Agent> A) 
  { 
          Agents.Remove(A); 
          A->GetCurrentLoc()->AgentLeaves(A); 
          A->Kill(); 
  } 

  // ...  and, in agent.cpp ... 
  void Agent::Kill() 
  { 
  // Set current location to Nul - NB dot syntax! 
          CurrentLoc.SetNull(); 
  // Set current state to zombie 
          m_bZombie = true; 
  } 

이것은 포인터가 아닌 개체 변수를 사용하여 수행한다면, 우리는 선택의 여지가 없을 것이다 그러나이 단계에서 에이전트를 삭제합니다. 우리는이 에이전트를 참조하는 수도 일부 개체를 업데이트 놓쳤다 경우, 우리 모든 너무 익숙한 결과, 그냥 삭제 에이전트 미래 접근 가능할 것이다하는 상황에서 남아있을 것이다.

이 기술의 단점 cople가 있습니다. 하나의 문제는 이것의 해석에서 발생합니다.에이전트가 위치를 입력하면, 다음과 같이 남아있다 이전 및 새 위치를 알리기 위해 개체 자체를 에이전트에 대한 편리 할 것입니다 :

 

  void Agent::Enter(ObjVar<Location> NewLoc) 
  { 
          CurrentLoc->AgentLeaves(this); // NB! 
          CurrentLoc = NewLoc; 
          CurrentLoc->AgentEnters(this); // NB! 
  } 

그러나, 현재 에이전트 포인터가 C + + 포인터 에이전트가 아닌 객체 변수입니다.컴파일러는 위치 멤버 함수 호출 임시 개체 변수에이 포인터를 변환가 objVar는 그것이 포장 에이전트에 첫 번째 참조이며, 함수의 끝에서 범위를 벗어나면 생각합니다 래핑 된 에이전트 포인터가 삭제됩니다! 우리가 objVar 템플릿 생성자의 선언에 새 명시 적 키워드를 사용을 통해 자동으로 변환하는 컴파일러에서 방지 할 수 있습니다 :

  template <class T> 
  class ObjVar 
  { 
  public: 
          ObjVar(); 
          explicit ObjVar(T* pT); 
  ... 

이 적어도 실수로 변환 및 포장 오브젝트의 결과의 삭제 가 발생하지 수 있다는 것을 의미합니다. 그러나관용구 에 구멍을 두고 수행 하고, 우리 는 이러한 상호 작용 에 대한 책임 세 번째 개체를 만들 강제 : 현재의 경우이 세 번째 객체 는 물론 에이전트와 ObjVars 같은 위치 모두의 ID를 보유해야 합니다. 놀랍게도, 공간 이미 여기에 짧은 대로 후속 기사 기다려야 할 것이다 이 문제를 해결 하는 방법 이 있다.

두 번째 문제는 상호 참조를 보유 개체 에서 발생합니다. 효과 ,좋은 구식 메모리 누수 - 이 이제까지 이러한 개체가 보유하고있는유일한 참조 될 경우 , 우리는가 objVar 메커니즘에 의해 회수 될 수 없다 개체를 끝낸다. 상황은 세 개 이상의 링크 의 사이클 을 확장합니다. 이 문제를 방지하려면 , 우리는 적어도 하나의 객체가 사이클 을 깨는 에 대한 책임이 있다고 주의해야합니다 .

그럼에도 불구하고 , 이러한 형식의 개체 변수 개체 의 컬렉션을 관리 넘어 템플릿 의 힘을 보여줍니다. 참조 카운트 스마트 포인터 , 공유 의 참조 카운팅 및 관리 의 부담 에서 개별 객체 instances.Although 의 삭제 에 대한 걱정 에서 우리 대부분 무료로 촬영 하여 연결을 구현 포인터의 유연성을 제공 하지만,별도의 간접 는 소폭 함수 호출 을 느린 코드 크기만 페널티템플릿 클래스 멤버 함수를 전문 에서 발생 : 특히 , ObjVars 는포인터를 포함하고 있다는 사실 에 전달하고 함수 에서 반환 하기가 효율적 . 추상 기본 클래스 의 특수 인스턴스를 래핑하는 개체 변수 를 사용하여 , 우리는 다형성 에서 자동 메모리 관리 의 측정 에서 모두 혜택을 누릴 수 있습니다.