본문 바로가기

정보정보

스마트 포인터 - CPP

스마트 포인터란?

흠...

이번 시간엔 꽤나 영특한 녀석을 소개하고자 한다.

 

바로 '스마트 포인터' 란 놈인데

게으르고 주위 깊지 못한 본인과 같은 프로그래머 들에겐

한 줄기 단비와 같은 존재가 아닐수 없다.

 

 

* 첨부된 예제에서 사용된 스마트 포인터는 boost 라이브러리에 속한

Shared Pointer( shared_ptr ) 란 놈이다. 따라서 이놈을 사용하려면 미리 라이브러리를

설치 해야 하는데 이곳에서 알아보도록 ( http://www.boost.org )

 

 

 

동적 할당의 그림자 

 

 

C++ 과 같은 객체 지향 언어에서 객체를 동적 할당을 이용해 생성하는 것은

아주 자연스러운 일이고 그 만큼 비일 비재하게 발생하게 된다.

 

그러나 모든 일에는 책임이 따르는 법, 똥을 쌌으면 누군가는 치워야하는 것이다.

즉, 객체에 대해서 동적 할당된 메모리는 사용이 끝났으면 반드시 Heap 영역에 반납해야한다. 

 

그렇지 않고 이것을 그대로 방치해 둔다면 소위 메모리 누수( Memory Leak ) 라는 

끔찍한 현상이 발생하고 이것이 반복적으로 누적돼 메모리가 줄줄 센다면 

동작중인 프로그램이 메모리 부족 현상으로 갑작시리 비명 횡사( 멈추거나 혹은 죽거나 )할 것이고

만일, 우연히 지나가는 회사 사장이 그 장면을 목격하기라도 하는 날엔... 어휴

 

물론 이렇게 말할수 있다.

" 훗... 그 까짓거, 뭐 어려운 일도 아니군. 쓴 다음에 꼬박 꼬박 반납하면 되지 않은가? "

 

당연히 옳은 소리다.

그런데 누구는 자신의 무능함을 뽐내기 위해 프로그램에 일부러 버그를  심어 놓겠나?

사람은 누구나 실수를 저지른다. 그 때문에 사람의 실수를 줄여가기 위한 제약이나 안전 장치

가 추가돼는 방향으로 프로그래밍 체계는 발전하고 있는것이며

객체 지향 방법론 또한 그런 연유로 기존 프로그래밍 방식을 제치고 대세가 된 것이다.

 

더군다나 지금 소개하게 될 뛰어난 안전장치가 있다면 구지 그것을 팽개치고

위험한 길( 버그가 발생할 확률이 높은 )을 택할 이유는 없지 않은가?

 

 

 

 

 

메모리 누수( Memory Leak )

 

 

쉽게 말해 힙으로 부터 메모리를 예약( 동적 할당 ) 한 후에

사용이 끝났음에도 그것을 다시 힙에게 돌려주지 않고, 끝내는 돌려주고 싶어도

돌려줄수 없는 상황이 올때 보통 흔히들 " 메모리가 세는군. 젠장!!! "이라고 말한다.

 

뭐 더 이상의 말은 의미가 없으니 바로 예제 들어간다.

 

//---------------------

// class CEnemy

//---------------------

class CEnemy{   };

...

 

//-------------------------------

//   Client Code

//-------------------------------

void Func()

{

   ...

   CEnemy* pEnemy = new CEnemy( ... );   // CEnemy 객체를 생성( 동적할당 )한다

   ...

   /* 여기서 생성된 CEnemy 객체를 지지고 볶는다 */

   ...

   delete pEnemy;   // 다 썼으면 반드시 동적할당 받은 메모리를 삭제( Heap 에게 돌려줌 ) 한다

}

 

이것이 기본 적인 원칙이다.

반드시 new 연산자를 통해 동적 할당을 이용했다면 delete 연산자로 동적 할당을 해제해야한다.

물론 위의 예제에서 이 원칙을 지키는 것은 아주 간단하게 보인다.

 

그러나 객체를 생성한 시점 부터 삭제 되기 전까지, 즉 객체를 실제로 활용하는 부분에서

Func() 함수에서 리턴하게 돼는 부분이 존재 한다면 어떻게 될것인가?

 

void Func()

{

   ...

   CEnemy* pEnemy = new CEnemy( ... );   // CEnemy 객체를 생성( 동적할당 )한다

   ...

   if( Condition == ERROR )

   {

      return;   // 이렇게 함수를 빠져 나갈 경우, 메모리 누수가 발생

   }

   ...

   delete pEnemy;   // 다 썼으면 반드시 동적할당 받은 메모리를 삭제( Heap 에게 돌려줌 ) 한다

}

 

즉, 메모리를 해제 하기 전에 빠져 나갈 여지가 자의든 타의든 존재하게됀다는 것이다.

 

뭐 해당 함수를 빠져 나가는 모든 경우에 객체 소멸 코드를 넣을수도 있지만

아무래도 이런 부분이 많아지다 보면 실수하고 빼먹을 확률이 높아지는 것이 사실이다.

 

객체를 생성한 뒤에 구지 delete 연산을 명시적으로 호출하지 않더라도

해당 함수 영역을 빠져 나갈때 알아서 객체가 소멸돼는 기능이 있으면 위에서와 같은

쓰잘데기 없는 고민으로 짧지도 않은 인생을 낭비할 필요가 없을것이다.

 

이럴때 써먹으라고 Smart Pointer 가 있는 것이다!!! 

 

 

스마트 포인터( Smart Pointer )

 

스마트 포인터는 어떤 동적할당 된 객체를 가르키고 있다가

-보통 스마트 포인터를 만들시 가르킬 객체의 포인터로 초기화 한다.

 

만일 자신이 속한 블럭(혹은 함수 )에서 빠져 나갈시 자동으로 자신이 물고 있는

객체 포인터에 대해 자동으로 소멸자( delete )를 호출시킨다.

 

그러니까 쉽게 말해 일단 사용할 객체를 동적 할당으로 생성한 뒤에 이것을

스마트 포인터가 가르키가 한다면 그 이후로 객체 소멸에 대해 전혀 신경쓸 거리가

없어진다는 얘기다.

 

void Func()

{

   ...

   SmartPointer< CEnemy > sp( new CEnemy( ... ) );    // 이제 부터 구질 구질한 일들과 안녕이다.

   ...

}

 

기본적인 원리는 상당히 간단하다.

위에서 보다시피 스마트 포인터는 자동 지역 객체( Auto Local Instance )이고 이놈들의 특징은

자신이 속한 함수( 혹은 블럭 )이 호출될시 스택에서 생성되며

자신이 속한 블럭 범위에서 벗어날시 자동으로 스택에서 삭제 된다는 것이다.

 

그런 자동 지역 객체( 변수 )의 성질 때문에 스마트 포인터의 소멸자에서 

가르키고 있던 객체가 값이 있다면 그것에 대해 delete 연산을 한번 호출해주면 땡이다.

 

 

//----------------------------------------------------------------
// class CSmartPointer
//----------------------------------------------------------------
// 한번 만들고 싶어 대충 휘갈긴 스마트 포인터
//----------------------------------------------------------------
#ifndef _C_SMART_POINTER_H_
#define _C_SMART_POINTER_H_


template< typename T >
class CSmartPointer
{
 //--------------------------------------
 // 공용 인터페이스
 //--------------------------------------
 public:
  //-------------------
  // 연산자 오버로딩
  //-------------------
  operator T*(){  return m_Pointer; }  // 암시적 타입 변환 연산자
  T* operator->(){ return m_Pointer; }  // 역 참조 연산자
 //--------------------------------------
 // 생성자 및 소멸자
 //--------------------------------------
 public:
  explicit CSmartPointer( T* pInstance )
  {  
   if( pInstance )
   { m_Pointer = pInstance; }
  }

  ~CSmartPointer()
  {
   if( m_Pointer )
   { delete m_Pointer; } 
  }
 //--------------------------------------
 // 멤버 변수
 //--------------------------------------
 private:
  T* m_Pointer;


}; // class CSmartPointer

 

#endif //_C_SMART_POINTER_H_

 

 

쩝... 물론 이정도로 단순하진 않겠지만 아마 기본원리는 이럴 것이다.

 출처:http://blog.naver.com/kzh8055?Redirect=Log&logNo=140059062664
 More Effective CPP

 

실전에서 응용하기

다양한 스마트 포인터에 대해서 정리해 보아요 ~


**차례**


1.auto_ptr ( STL )

2.scoped_ptr ( boost library )

3.scoped_array ( boost library )

4.shared_ptr ( tr1 or boost library )

5.shared_array ( boost library )

6.weak_ptr  ( boost library )

7.intrusive_ptr  ( boost library )

8.CComPtr (ATL)

9.CComQIPtr (ATL)

10._com_ptr_t (COMDEF.H )

11.CComVariant (ATL)

12.CComBSTR (ATL),

13._bstr_t

14._com_ptr_t

15._variant_t



1.auto_ptr ( STL )



2.scoped_ptr ( boost library )



scoped_ptr class template


scoped_ptr 는 동적으로 할당되어진 객체포인터 메모리를 관리해주는 클래스 템플레이트이다.

(동적으로 할당되어진 객체들은 C++ new 연산자에 의해 할당되어진것을 말함)

scoped_ptr 내에서 관리되는 객체 포인터는 scoped_ptr 객체 파괴시 소멸자에서 삭제되어지거나

명시적으로 reset 함수를 호출함으로써 삭제할 수있다.


샘플한번 보자.



#include 〈boost/scoped_ptr.hpp〉

#include 〈iostream〉


struct Shoe { ~Shoe() { std::cout << "Buckle my shoe\n"; } };


class MyClass {

    boost::scoped_ptr ptr; 

  public:

    MyClass() : ptr(new int) { *ptr = 0; }

    int add_one() { return ++*ptr; }

};


int main()

{

    boost::scoped_ptr x(new Shoe);

    MyClass my_instance;

    std::cout << my_instance.add_one() << '\n';

    std::cout << my_instance.add_one() << '\n';


}


scoped_ptr 템플레이트는 아주 단순한 스마트 포인터이다. 

scoped_ptr은 관리되는 포인터에 대한 소유권 공유나 소유권 이동(shared-ownership or transfer-of-ownership) 없이 "자원 획득시 초기화"(resource acauisition is initialization)라는 C++의 목적에 맞도록 구현된 가장 기본적인 포인터관리 클래스이다.


클래스 정의를 보면 다음과 같다.


namespace boost {


  template class scoped_ptr : noncopyable {


   public:

     typedef T element_type;

     explicit scoped_ptr(T * p = 0); // never throws

     ~scoped_ptr(); // never throws


     void reset(T * p = 0); // never throws


     T & operator*() const; // never throws

     T * operator->() const; // never throws

     T * get() const; // never throws

     

     operator unspecified-bool-type() const; // never throws


     void swap(scoped_ptr & b); // never throws

  };


  template void swap(scoped_ptr & a, scoped_ptr & b); // never throws


}


scoped_ptr의 이름이나 구현시 noncopyable 을 상속한 것을 보면 현재 범위에서만 유일하게 소유권을 유지 할 수 있도록 하고싶은 의도를 알수있다. scoped_ptr은 복사되어지면 안되는 포인터들에 대해서는 noncopyable 을 상속하여 구현했기때문에 shared_ptr 이나 std::auto_ptr보다 훨씬더 안전하다.


(*noncopyables는 복사 생성자와 복사대입연산자를 private에 선언한 클래스이다. private에 선언되어있기때문에 이 클래스를 상속하여 구현한 클래스들은 인스턴스간 복사생성자 또는 복사대입연산자를 호출 할 수 없된다. 즉 복사가 불가능하단것임)


scoped_ptr은 단순하기때문에 모든 연산들은 내장된 포인터만큼 빠르고 더많은 공간 오버헤드도 필요없다.


주의할점은 다음과 같다.

1.scoped_ptr을 C++ 표준 라이브러리 컨테이너와 함께 사용할 수 없다. 대신 이런 경우에는shared_ptr이 좋은 대안책이 될 것이다. 

2.동적으로 할당된 배열에 대한 포인터에대해서는 사용하지 사용할 수 없다.이런 경우 scoped_array가 좋은 대안책이 될 것이다.



3.scoped_array ( boost library )


scoped_array class template


scoped_array 클래스 템플레이트 C++ new[]연산자를 사용하여 동적으로 메모리를 할당한 배열에 대한 포인터 메모리를 관리해주는 클래스이다. scoped_array 내에서 관리되는 객체 포인터는 scoped_array  객체 파괴시 소멸자에서 삭제(메모리해제)되어지거나 명시적으로 reset 함수를 호출함으로써 삭제할 수있다.


char * sap = new char [ 100 ];
boost::scoped_array sa ( sap );

scoped_array 는 scoped_ptr과 마찬가지로 아주 단순한 스마트 포인터이다. 

scoped_array 는 관리되는 포인터에 대한 소유권 공유나 소유권 이동(shared-ownership or transfer-of-ownership) 없이 "자원 획득시 초기화"(resource acauisition is initialization)라는 C++의 목적에 맞도록 구현된 가장 기본적인 포인터관리 클래스이다.


클래스 정의를 보면 다음과 같다.

namespace boost {


  template class scoped_array : noncopyable {


    public:

      typedef T element_type;


      explicit scoped_array(T * p = 0); // never throws

      ~scoped_array(); // never throws


      void reset(T * p = 0); // never throws


      T & operator[](std::ptrdiff_t i) const; // never throws

      T * get() const; // never throws

     

      operator unspecified-bool-type() const; // never throws


      void swap(scoped_array & b); // never throws

  };


  template void swap(scoped_array & a, scoped_array & b); // never throws


}


scoped_array 의 이름이나 구현시 noncopyable 을 상속한 것을 보면 현재 범위에서만 유일하게 소유권을 유지 할 수 있도록 하고싶은 의도를 알수있다. scoped_ptr은 복사되어지면 안되는 포인터들에 대해서는 noncopyable 을 상속하여 구현했기때문에 shared_ptr 이나 std::auto_ptr보다 훨씬더 안전하다.


scoped_array은 단순하기때문에 모든 연산들은 내장된 포인터만큼 빠르고 더많은 공간 오버헤드도 필요없다.



주의할점은 다음과 같다.

1.scoped_array을 C++ 표준 라이브러리 컨테이너와 함께 사용할 수 없다. 대신 이런 경우에는shared_array이 좋은 대안책이 될 것이다. 

2.동적으로 할당된 단일 포인터(single object pointer)에대해서는 사용할 수 없다.이런 경우 scoped_ptr 가 좋은 대안책이 될 것이다.


동적으로으로 할당된 배열을관리하기위해 scoped_array 대신 std::vecotor가 또 다른 방법이다.

std::vector 는 scoped_array에 비해 약간 더 비싸지만 훨씬 유용하다

배열사이즈가 fixed되어있는경우라면 boost::array를 사용하면 된다.



4.shared_ptr ( tr1 or boost library )

 

shared_ptr 템플레이트 클래스는 일반적으로 C++ new 연산자를 통해 동적으로 할당되어진 객체에 대한 포인터를 저장한다. (*일반적이란말의 의미는 shared_ptr은 삭제자가 있어서 반드시 new로 할당된 포인터만 관리할 수있는것이 아니다. 삭제자를 지정하여 shared_ptr소멸시 delete가 아닌 삭제자가 호출되도록 할 수있기 때문에 일반적이란 말을 사용 하였다.)

 

관리되는 포인터는 포인터를 가리키는 shared_ptr의 참조카운트가 0일 때 파괴 되어지거나 reset 함수가 호출되어질 때 삭제 되어짐을 보장한다. 모든 shared_ptr은 C++표준 라이브러이 요구사항인 CopyConstructible(복사 생성가능) 와 Assignable(대입가능) 를 만족한다.
(scoped_ptr과 ,scoped_array는 noncopyable을 상속하기때문에 위의 표준사항을 만족하지 않는다. shared_ptr은 위의 표준사항 을 만족하기위해 참조 카운팅 방식으로 구현을 했다.)

 

따라서 shared_ptr은 표준 라이브러리 컨테이너에서 사용할 수있다.
(auto_ptr, scoped_ptr, scoped_array등에서 사용할 수 없음)

 

비교 연산자( ==, != 등)들은 shared_ptr가 표준 라이브러리의 연관 컨테이들(map, set, multiset, multimap)과 함께 사용 할 수 있도록 제공되어진다.

 

일반적으로 shared_ptr은 동적으로 할당되어진 배열에 대한 포인터를 올바르게 유지 할 수 없다.
이런 경우에는 shared_aary가 적격일 것이다.


shared_ptr 은 현재 TR1(C++ Library Technical Report)에 포함되어 있고 가장 최근의 TR1은
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1745.pdf 에서 볼수있다.

shared_ptr 템플레이트 클래스의 선언은 아래와 같다.

 

 

namespace boost {

  class bad_weak_ptr: public std::exception;

  template class weak_ptr;

  template class shared_ptr {

    public:

      typedef T element_type;

      shared_ptr(); // never throws
      template explicit shared_ptr(Y * p);
      template shared_ptr(Y * p, D d);
      template shared_ptr(Y * p, D d, A a);
      ~shared_ptr(); // never throws

      shared_ptr(shared_ptr const & r); // never throws
      template shared_ptr(shared_ptr const & r); // never throws
      template shared_ptr(shared_ptr const & r, T * p); // never throws
      template explicit shared_ptr(weak_ptr const & r);
      template explicit shared_ptr(std::auto_ptr & r);

      shared_ptr & operator=(shared_ptr const & r); // never throws 
      template shared_ptr & operator=(shared_ptr const & r); // never throws
      template shared_ptr & operator=(std::auto_ptr & r);

      void reset(); // never throws
      template void reset(Y * p);
      template void reset(Y * p, D d);
      template void reset(Y * p, D d, A a);
      template void reset(shared_ptr const & r, T * p); // never throws

      T & operator*() const; // never throws
      T * operator->() const; // never throws
      T * get() const; // never throws

      bool unique() const; // never throws
      long use_count() const; // never throws

      operator unspecified-bool-type() const; // never throws

      void swap(shared_ptr & b); // never throws
  };

  template
    bool operator==(shared_ptr const & a, shared_ptr const & b); // never throws

  template
    bool operator!=(shared_ptr const & a, shared_ptr const & b); // never throws

  template
    bool operator<(shared_ptr const & a, shared_ptr const & b); // never throws

  template void swap(shared_ptr & a, shared_ptr & b); // never throws

  template T * get_pointer(shared_ptr const & p); // never throws

  template
    shared_ptr static_pointer_cast(shared_ptr const & r); // never throws

  template
    shared_ptr const_pointer_cast(shared_ptr const & r); // never throws

  template
    shared_ptr dynamic_pointer_cast(shared_ptr const & r); // never throws

  template
    std::basic_ostream & operator<< (std::basic_ostream & os, shared_ptr const & p);

  template
    D * get_deleter(shared_ptr const & p);
}

 

머 무지 많은데 사용방법을 살펴보면서 알아가도록 하자.


// 아래코드는 의사코드이며 컴파일러로 확인해보지 않았음.

 

 

#include
#include

class MyClass
{
public:
 MyClass( int i ) ;
} ;


void destroy( HWND hwnd ) {
 DestroyWindow(hwnd) ;
}

void printf_myclass( MyClass& c )
{
}

// 할당된 객체(리소스)를 스마트 포인터를 이용하여 관리하는 습관은 매우 좋은 습관이다.
// 예외에 안정적이고 메모리 누수에 신경쓸일이 사라지기 때문이다.
// 보통 스마트 포인터를  STL 컨테이너에 사용하는경우 scoped_ptr, auto_ptr, scoped_array는
// 절대 안된다. scoped_ptr, scoped_array는 컨테이터의 멤버 함수또는 algorithm관련 generic
// 함수를 사용하는 경우 컴파일타임시 에러가 발생될 가능성이 크고 auto_ptr은 컴파일 에러는 발생
// 되지 않치만 그 특성( auto_ptr 부분 참고)상 사용하면 안되다.
// 따라서 shared_ptr을 사용하는것이 좋은 선택이다.(단, 순환참조가 일어나지 않는다는 가정하에서다.)

// 스마트 포인터를 typedef하면 타이핑하는 시간도 절약되고 가독성도 높아진다.
typedef std::tr1::shared_ptr MyClassPtr ;
typedef std::vector CArrClass ;

typedef std::tr1::shared_ptr CWindowHandlePtr ;

int main()
{
 CArrClass arr ;

 // 1.shared_ptr은 STL컨테이너에 사용가능하다.
 MyClassPtr myclass( new  MyClass(1) ) ;
 arr.push_back( myclass );

 // 2.reset
 MyClassPtr myclass_reset( new  MyClass(2) ) ;
 // reset 함수는 기존에 소유된 포인터를 삭제하고 새로운 메모리 포인터를 소유하게 된다.
 myclass_reset.reset( new MyClass(3) ) ;  //  MyClass(2) 가 삭제되고 새로 MyClass(3)를 소유함.


 // 3.
 // shahred_ptr은 삭제자를 지정하여 reset 또는 소멸자 호출시 삭제자로 소유된 객체를 삭제한다.
 // 소멸자 호출시 (ref count = 0 인겨웅) destroy() 함수가 호출됨.
 CWindowHandlePtr handle( hWnd, destroy ) ;

 // 4.STL 컨테이너와 알고리즘의 기본 데이터 관리법은 복사해서 들어가고 복사해서 나오는것
 // 따라서 복사생성자와 복사대입연산자가 잘정의된 (deep copy) 클래스가 필요하다.
 // shared_ptr은 참조카운팅 방식으로 구현되었으므로 위에 모든것을 만족한다.
 // scope_ptr은 noncopyable로 인해 아래코드가 가능하지 않다.
 for_each( arr.beging(), arr.end(), printf_myclass ) ;

 
 // 5.
 // 더이상 메모라 해제에 대해서 신경쓸 필요가 없다.


 return 0 ;

}

 


하지만 shared_ptr이 만능은 아니고 이 스마트 포인터 역시 사용할 때 주의할 점이 있다

1.변수명없는(unnamed) shared_ptr을 사용하지 말것.

아래코드가 왜 위험한지 생각해보자

 

void f(shared_ptr, int);
int g();

void ok()
{
    shared_ptr p(new int(2));
    f(p, g());
}

void bad()
{
    // 변수명없이(unnamed shared_ptr) 사용
    f(shared_ptr(new int(2)), g());
}


위의 ok함수는 1번 지침을 잘따른것이고 반면 bad는 메모리누수가 발생할 여지가 있다.
왜냐하면 함수의 인수들이 컴파일러에 의해 평가되어지는 순서는 컴파일러마다 다르다.
만약 new int(2)가 처음에 평가되어지고 shared_ptr이 그 포인터의 소유권을 넘겨받기전에 g()가 두번째로 수행되어진다고가정 할 때 그리고 이때 g()가 호출되면서 예외가 발생되면 new int(2)의 메모리는 우주 미아가 된다. (이것에 대한 내용은 스캇마이어스님의 Effectvie 시리즈와  Herb Sutter님의 Exceptional 시리즈를 보면 좀더 많은 내용을 알수있다.)


2.shared_ptr이 서로 참조하는 순환고리를 만들지 말것.
shared_ptr은 레퍼런스 카운팅(참조 카운팅)을 사용하기때문에 shared_ptr이 인스턴스을 순환참조 서로를 서로가 가리키는경우)가  생기게 되어 포인터를 삭제하지 못하는경우가 발생되기 쉽다.
이런 순환참조가 생기는 문제에 대해서는 weak_ptr을 사용해라.

 

 

3.같은 scope에서 두개의 스마트포인터 객체가 동일한 포인터를 소유하지 말것.

 

 MyClass* m = new MyClass(1) ;
 std::tr1::shared_ptr sp1( m ) ;
 std::tr1::shared_ptr sp2(m)   ;

 

sp2에서 먼저 소유된 포인터를 삭제한 후 sp1에서 이미 삭제된 포인터를 삭제하려하는 일이 발생된다.

 

**이부분 나중에 보충...**


more detail information for shared_ptr :

 

1.http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=7521&ref=7521
2.http://sanaigon.tistory.com/72?srchid=BR1http%3A%2F%2Fsanaigon.tistory.com%2F72
3.http://yesarang.tistory.com


5.shared_array ( boost library )


shared_array 클래스 템플레이트는 C++ new[] 연산자와 함께 동적으로 할당된 배열에 대한 포인터를 저장한다. shared_array의 참조카운트가 0 이되거나 reset() 함수를 호출함으로써 저장된 포인터가 삭제되어진다.

모든 shared_array 은 C++표준 라이브러이 요구사항인 CopyConstructible(복사 생성가능) 와 Assignable(대입가능) 를 만족한다. 따라서 표준 라이브러리 컨테이너와 함게 사용되어질 수 있다. 비교 연산자들은 shared_array가 표준라이브러리의 연관 컨테이터들에 사용되어지기위해서 제공되어진다.

보통 shared_array는 new [] 가 아닌 new 로 할당되어진 객체에 대한 포인터를 올바르게 유지할수없다. 이런경우에는 shared_ptr을 사용할것.

 

 

std::vector < std::shared_ptr< object pointer > >  보다 더 무거우나 좀더 유용한 shared_array를 사용하는것은 또하나의 대안이다. 


6.weak_ptr  ( boost library )



7.intrusive_ptr  ( boost library )



8.CComPtr (ATL)


먼저 아래 작성한 글은 지극히 주관적임을 분명 밝혀둔다.


MS컴파일러는 몇몇 COM 타입들을 지원하기 위해 사용되는 표준 클래스들이 있는데 _bstr_t /  _com_error / _com_ptr_t / _variant_t 이다. 보면 알겠지만 CComBSTR, CComVariant, CComPtr과 하는역활은 거진 비슷하고 메소들 또한 비슷한 일을 하는 함수들을 내장하고 있다.


사실 하는일이 비슷한동일한 클래스들이다.

근데 왜 MS에서 비슷한 역활을 하는 클래스들을 이렇게 따로 만들어 놨는가 ? 이다.


_bstr_t /  _com_error / _com_ptr_t / _variant_t는 사실 최초  COM 모듈을 생성하기위해 

좀더 편리하게 사용하기위한 유틸리티(헬퍼)클래스들이었으나 COM모듈을 구현하기란 웬 만한 C++ 개발자에

겐 너무도 어려웠다. 그래서 ATL(Active Template Library)를 설계 하였으며

이 후 부터 우린 COM또는 요즘에 말도 많고 탈도 많은 ActiveX Control을 쉽게 구현하게 된것이다.


ATL에서 사용하는 유틸리티(헬퍼)클래스들이 바로 CComBSTR, CComVariant, CComPtr 인것이다.

분명 표준클래스에 관여한 팀과 ATL을 관장했던 팀은 서로 달랐을것이다.

따라서 자신들만의 클래스들을 따로 만들게 된것이다. 

사실 ATL팀 구성원들은 아마 _bstr_t /  _com_error / _com_ptr_t / _variant_t들의 기능이 좀 맘에 안들거나 또는 클래스명이ATL과 좀 안어울린다(모두 소문자라???)고 생각해서 다시 작성하게 된것일수 있다.


이런것은 많이 찾아볼수있다.

어떤 자료구조의 데이터개수를 리턴하는 함수명들을 생각해본적이 있는가 ?

MS에서 각 언어마다 제공되는것들이 다 다르다.


size()

count()

length()

.

.

이유는 모두 담당하는 팀들과 구성원들이 다르기 때문이다.



9.CComQIPtr (ATL)

 


11.CComVariant (ATL)



12.CComBSTR (ATL)


 CComBSTR 클래스는 BSTR 의 래퍼 클래스이다.
즉 BSTR 타입을 유용하게 사용하기위한 함수들을 정의한 클래스란 말이다.
BSTR 타입은 은 VC에 다음과 같이 정의되어져있다.

 

typedef LPWSTR BSTR;
typedef WCHAR OLECHAR;
typedef OLECHAR* BSTR;

 

즉 BSTR은 문자당 2바이트를 차지하는 유니코드형 문자 타입을 typedef 한 것 뿐이다.
포인터형이기 때문에 사용 할 때 문자열길이 만큼 할당하고 다 사용한 다음에는 할당 한 메모리를 해제 해야한다. CComBSTR은 이 할당과 해제를 클래스 내부에서 관리해주기 때문에 우리들은 그냥 메모리 신경쓸 필요 없이 사용 하기만 하면 된다.
고로, CComBSTR은 문자열 포인터에 대한 스마트 포인터 그리고 그와 관련된 메소드들을 포함한 클래스인것이다.


Programming with CComBSTR

 

CComBSTR은 BSTR데이터 타입에 대한 래퍼클래스이다. CComBSTR클래스가 유용한 클래스임에는 분명하지만 사용시 주의할 몇가지 경우가 있다.

 

Conversion Issues(변환문제)

 

몇몇 CComBSTR메서드들은 ANSI 문자열 인수를 UNICODE로 자동적으로 변경 할 것이다. 그 메서드들은 항상 유니코드 문자열을 리턴한다. 출력 문자열을 ANSI로 변환하기위해서는 ATL 변환 클래스들을 살펴봐라.  (ATL and MFC String Conversion Macros.- 대부분 CA2AEX, CA2WEX, CW2AEX, and CW2WEX 식으로 되어있다.))

 

// CComBSTR클래스를 선언해라. 인수가 ANSI임에도 불구하고 생성자가 그것을 UNICODE로 변경한다.
CComBSTR bstrMyString( "Hello World" );

// 문자열을 ANSI 문자여롤 변경한다.
CW2CT szMyString( bstrMyString );
// ANSI 문자열을 디스플레이한다.

MessageBox( NULL, szMyString, _T("String Test"), MB_OK );


CComBSTR 생성시 문자열을 사용 할 때 와이드 캐릭터 문자열을 사용해라 이것은 불필요한 변환을 막을 수 있다.

 

// 아래는 ANSI를 Unicode 로 변환하는 불필요한 변환이 있다.
CComBSTR bstr("Test");

// 하지만 아래는 컴파일 때 Unicode 문자열로 사용하여 불필요한 변환이 없다.
CComBSTR bstr(L"Test");


Scope Issues(범위 문제)

 

CComBSTR는 선언된 변수가  블록( { } ) 을 벗어날때 소유된 리소스들을 모두 해제한다.
근데 이때 CComBSTR에서 문자열 포인터를 리턴하는 함수들을 사용 할 때 주의해야 한다
왜냐하면 이미 그 리턴된 포인터의 메모리가 해제된(CComBSTR은 블록을 벗어날대 메모리를 자동으로 해제하는 스마트 포인터라는 점) 무효한 포인터르르 사용할 가능성이 있기`때문이다.

 

// 잘못된 사용법을 보여 줌
BSTR * MyBadFunction()
{
   // BSTR포인터를 정의한다.
   BSTR * bstrStringPtr;

   // CComBSTR 객체를 생성한다.
   CComBSTR bstrString("Hello World");

   // 문자열을 대문자로 변경한다.
   bstrString.ToUpper();

   // 포인터를 대입한다.
   * bstrStringPtr = bstrString;
   // 포인터를 리턴한다.. ** 아주 못된 짓거리임. **
   return bstrStringPtr; // MyBadFunction함수를 벗어나면서 CComBSTR객체가 소유한 메모리를      // 제하기 때문에 리턴된 포인터는 무효한 포인터가 딘다.
}


// 올바른 사용법
HRESULT MyGoodFunction(/*[out]*/ BSTR* bstrStringPtr)
{
   // CComBSTR 객체를 생성한다.
   CComBSTR bstrString("Hello World");
   // 문자열을 대문자로 변경한다.
   bstrString.ToUpper();
   // 새롭게 복사된 문자열을 리턴한다.
   return bstrString.CopyTo(bstrStringPtr);
}


Explicitly Freeing the CComBSTR Object( CComBSTR 객체의 명시적 메모리 해제)

 

만약 객체가 블록을 벗어나기 전에 CComBSTR 객체안에 담겨진 문자열을 명시적으로 해제 한다면 CComBSTR 객체는 무효하다. 따라서 명시적으로 해제하지 말것.

 

// CComBSTR 객체를 선언하고
CComBSTR bstrMyString( "Hello World" );
// 명시적으로 문자열을 해제하고
::SysFreeString(bstrMyString);
// CComBSTR객체가 범위를 벗어날때 문자열은
// 두번 해제될것이고 그것은 유효하지 않은것을 해제하게 된다.

 

 

Using CComBSTR Objects in Loops(루프안에서 CComBSTR객체 사용하기)

 

CComBSTR은 += 또는 Append 메소드처럼 특정 연산들을 수행하기위해 버퍼를 할당 하기 때문에 루프안에서 문자열 조작 함수 사용을 권장 하지 않는다. 이런 상황은 CStringT가 더 낳은 성능을 제공한다.

 

// 이것은 CComBSTR 객체를 사용하기위한 효과적인 방법이 아니다.
// 대신 CString을 사용해라.
CComBSTR bstrMyString;
while (bstrMyString.Length()<1000)
   bstrMyString.Append(L"*");

 

Memory Leak Issues(메모리 누수 문제)

 

[out] 파라미터가 필요한 함수에 초기화된 CComBSTR의 주소를 전달하는것은 메모리 누수의 원인이 된다. 아래 예제를 보면 문자열 "Initilaized" 를 유지하기위해 할당되어진 문자열은 OutString 함수가 문자열로 변경될때 메모리 누수가 발생한다.

즉 함수 파라미터가 [out]으로 된것은 메모리 누수가 항상 발생된다.

 

CComBSTR bstrLeak(L"Initialized");
HRESULT hr = OutString(&bstrLeak);

 

그 누수를 피하기 위해 [out]파라미터에 주소를 전달하기전에 CComBSTR 객체의 Empty 메서드를 호출하여라. 만약 함수의 파라미터가 [in, out] 인경우에는 메모리 누수가 일어나지 않음을 명심 해라.


13._bstr_t


 _bstr_t : BSTR 타입에 유용한 연산자들과 함수들을 제공하는 클래스이다.

이 클래스는 적절한 때에 SysAllocString , SysFreeString , 그리고 다른 BSTR API들을 호출하여 

리소스 할당과 해제를 관리한다. _bstr_t 클래스는 과도한 오버헤드를 피하기위해 레퍼런스 카운팅(참조카운팅:reference counting)을 사용한다. 





14._com_ptr_t


_com_ptr_t 템플레이트 클래스는 COM 인터페이스 포인터를 관리하는 스마트 포인터이다.

_com_ptr_t 클래스는 IUnknown 인터페이스의 멤버함수인 QueryInterface, AddRef, and Release 호출를 통하여 자원을 할당과 해제를 관리한다.


스마트 포인터는 보통 _com_ptr_t pMyInterface 처럼 사용하지만 코드 가독성과 타이핑 압박을 줄이기 위해서 _COM_SMARTPTR_TYPEDEF  매크로를 사용하여 특수화된 버전을 사용한다.


_COM_SMARTPTR_TYPEDEF 매크로는 아래처럼 되어있다.

#define _COM_SMARTPTR        _com_ptr_t

#define _COM_SMARTPTR_TYPEDEF(Interface, IID) \

    typedef _COM_SMARTPTR \

            Interface ## Ptr



위 매크로는 인터페이스 이름과 IID를 필요로한다. IMyInterface 인터페이스가 있다고 가정 할 때

_COM_SMARTPTR_TYPEDEF(IMyInterface , __uuidof(IMyInterface));


위와같이 선언하면 IMyInterface 인터페이스에 대한 _com_ptr_t의 특수화 버전인 IMyInterfacePtr 가 선언된다. 그럼 우린 _com_ptr_t pMyInterface 처럼 사용할 필요없이 단순히 

IMyInterfacePtr pMyInterface 처럼 사용할 수 있게된다.


쉽게 풀어쓰면

_COM_SMARTPTR_TYPEDEF(IMyInterface , __uuidof(IMyInterface)) ; 는

typedef _com_ptr_t IMyInterfacePtr ; 와 동일한 코드가 된다.


샘플 예제를 보자


// 스마트포인터 미사용시.


IXMLDOMDocument2* pXMLDOMDocument2 = NULL ;

hr = CoCreateInstance(CLSID_DOMDocument40, NULL, CLSCTX_INPROC_SERVER, 

IID_IXMLDOMDocument2, (void**)&pXMLDOMDocument2) ; 

if (pXMLDOMDocument2 )

pXMLDOMDocument2->Release() ;


// 스마트포인터 사용시.

_com_ptr_t pXMLDOMDocument2 ;

hr = CoCreateInstance(CLSID_DOMDocument40, NULL, CLSCTX_INPROC_SERVER, 

IID_IXMLDOMDocument2, (void**)&pXMLDOMDocument2) ; 


//아래코드가 자동 호출된다.

//if (pXMLDOMDocument2 )

// pXMLDOMDocument2->Release() ;





15._variant_t






Assign : 

**레퍼런스 사이트**


1.http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm

2.

From...
http://blog.daum.net/shuaihan/15536608?srchid=BR1http%3A%2F%2Fblog.daum.net%2Fshuaihan%2F15536608