IT_Programming/C · C++

[C++] 스마트 포인터(Smart Pointer) 맛보기

JJun ™ 2009. 9. 15. 18:50



참고 : 포인터 실무 KIN   



 

※ 부스트 라이브러리 설치 (Boost library)

 

1. Visual Studio 2005 / 2008

   : 도구(tool) - 옵션(option) - 프로젝트(projects) - VC++ 디렉토리(VC++ directories) 선택

                         - 다음 파일의 디렉토리 표시 (Show directories for)

                         - 콤보박스에서 포함 파일(include files)에 boost library를 설치한 경로를 추가!

 

2. Borland C++ Compiler / C++ Builder X

   : bcc.exe가 위치하는 폴더 안에 bcc32.cfg 파일을 열어서 boost library를 설치한 디렉토리 경로를 추가!

     (ex) -I"C:\development\bcc\include;C:\boost 디렉토리"

            -L"C:\development\bcc\lib;C:\boost 디렉토리"

 

3. GNU C++ Compiler

   : 환경변수 CPLUS_INCLUDE_PATH에 boost library를 설치한 디렉토리 경로를 추가!

 

==================================================================================================

 

 

 

1. auto_ptr

    : 소유권 독점 방식으로 구동되는 스마트 포인터. 복사시 소유권 이전 되는 소유권 독점 방식을 취함.

    : C++ 표준 라이브러리에서 제공

    : 소유권 이전 금지 - const 키워드 사용 (ex) const boost::auto_ptr<MyClass> ptq(new MyClass);

    

    [특징]

    - 자동으로 포인터에 할당된 메모리를 해제한다.

    - 자동으로 포인터를 초기화한다. (디폴트 생성자)

    - 댕글링 포인터 문제를 해결한다.

 

    [주의사항]

    - auto_ptr은 STL 컨테이너(vector, list, map, hashtable 등)와 사용할 수 없다.

    - auto_ptr은 동적 배열에는 사용할 수 없다.

    - auto_ptr은 힙영역에 동적으로 할당된 메모리에만 적용 가능하다.

 

--------------------------------------------------------------------------------------------------

 

[예제]

 

 

--------------------------------------------------------------------------------------------------

 

#include <iostream>
#include <memory>
#include <boost/shared_ptr.hpp>

 

using namespace std;

 

class Car
{
   public:
       Car();
       ~Car();
       void race();
};

 

Car::Car()
{
    cout << "생성자" << endl;
}

 

Car::~Car()
{
    cout << "소멸자" << endl;
}

 

void Car::race()
{
    cout << "붕붕~!" << endl;
}

 

boost::shared_ptr<Car> createObject()
{
    return boost::shared_ptr<Car>(new Car());
}

 

int main(void)
{
    boost::shared_ptr<Car> a;
    boost::shared_ptr<Car> b;

   

    a = createObject();

    b = a;

   

    b->race();

   

    if(a.get() != NULL) // 실행되지 않는다. 소유권 이전 후 a는 NULL이 된다.
        a->race();


    return 0;

 

 

==================================================================================================

 

 

2. shared_ptr

 

    : 소유권이 공유되는 방식으로 동작하는 스마트 포인터

    : 동일한 객체를 가리키는 스마트 포인터의 개수를 센 후, 이 개수가 0이 될 때 객체를 삭제한다.

      즉, 동일한 객체를 참조하는 모든 스마트 포인터가 자신의 scope를 벗어날 때 공유 객체의 소멸이

      결정되는 방식.

 

    : STL 컨테이너에서 사용이 가능하다. (boost library를 반드시 설치하고, include 해야 한다.)

 

 

    [주요 특징]

    : 강한 참조의 성격을 가지고 있다.

    : 쓰레드에 안전하게 설계되어 있다. (그러나 쓰레드의 실행 순서에 따라 영향은 받으므로 설계가 중요!)

    : Thread Safety 해제 - 헤더 파일에 " #define BOOST_DISABLE_THREADS "를 적어준다.

 

 

    [주의사항]

    - 순환참조를 하면 안된다. (정확히 참조 카운트를 알 수 없기 떄문)

    - 이름이 없는 shared_ptr을 사용을 피하는 것이 좋다.

       (이미 동적 할당되어 생성된 객체가 삭제되지 않아서 메모리릭이 발생할 수 있기 때문이다.)

 

    - 스마트 포인터 생성시, 생성자를 직접 호출하거나 명시적 형변환만 가능하다.

       (암시적 형변환은 지원하지 않는다.)

 

    - 원본 포인터(raw pointer)를 직접 삭제 할 수 없다. (reset(), 재할당 혹은 스마트 포인터가 파괴 될 때

       자동으로 원본 포인터가 삭제되도록 작성하는 것이 좋다.)

 

 

--------------------------------------------------------------------------------------------------

 

[예제1]

 

 

 

#include <string>
#include <iostream>
#include <memory>
#include <vector>
#include <boost/shared_ptr.hpp>
// shared_ptr을 사용하기 위해서는 반드시 추가해야 한다.

 

using namespace std;

 

class Fruit
{
   public:
       Fruit(string name);
       ~Fruit();
       string getName();

  

   private:
       string name;
};

 

Fruit::Fruit(string name)
{
    this->name = name;
    cout << this->name + " 생성됨" << endl;
}

 

Fruit::~Fruit()
{
    cout << this->name + " 소멸됨" << endl;
}

 

string Fruit::getName()
{
    return name;
}

 

int main(void)
{
    typedef boost::shared_ptr<Fruit> fruit_sPtr; // 참조 카운팅 방식의 스마트 포인터
    vector<fruit_sPtr> vec;

   

    fruit_sPtr s = fruit_sPtr(new Fruit("Apple"));

   

    vec.push_back(fruit_sPtr(new Fruit("pear")));
    vec.push_back(fruit_sPtr(new Fruit("banana")));
    vec.push_back(fruit_sPtr(new Fruit("kiwi")));
    vec.push_back(s);

 

    cout << (*s).getName() << endl;

 

    return 0;

 

--------------------------------------------------------------------------------------------------

 

[예제 2]

 

 

 

#include <iostream>
#include <string>
#include <memory>
#include <boost/shared_ptr.hpp>

 

using namespace std;

 

class Shape
{
   public :
       Shape() {}
       ~Shape() {}
       void draw(string str);
};

 

void Shape::draw(string str)
{
    cout << str.c_str() << endl;
}

 

int main(void)
{
    typedef boost::shared_ptr<Shape> ShapePtr;

    Shape* shape1 = new Shape();
   

    ShapePtr sptr1(shape1);

    sptr1.get()->draw("Circle");

    cout << sptr1.use_count() << endl;  // use_count() : 참조 카운트를 반환한다.

 

    ShapePtr sptr2 = sptr1;
    cout << sptr1.use_count() << endl;

   

    sptr1.reset();
    cout << sptr2.use_count() << endl;

 

    return 0;
}

 

 

==================================================================================================

 

 

3. weak_ptr 

    : shared_ptr에서 순환참조로 발생하는 문제를 해결하기 위해서 사용하는 스마트 포인터

    : shared_ptr과는 달리 객체에 대한 소유권을 가지지 않고 객체를 사용한다.

    : 약한 참조의 성질을 가지고 있다.

      (객체가 살아있도록 유지시키지 않고, 단순히 객체가 살아 있는 동안 참조)

 

    : 자신이 가리키는 객체가 실제로 살아있는지 체크할 수 없으므로, 댕글링 포인터가 될 수 있다.(단점)

      (따라서 lock 함수를 사용해서 shared_ptr을 얻어올 때 반드시 리턴값이 NULL(0)인지 체크해야 한다.)

 

--------------------------------------------------------------------------------------------------

 

[예제]

 

 

 

#include <iostream>
#include <memory>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

 

using namespace std;

 

class Parent;

class Child;

 

typedef boost::shared_ptr<Parent> parentSPtr;
typedef boost::weak_ptr<Parent> parentWPtr;
typedef boost::shared_ptr<Child> childSPtr;

 

class Parent
{
   public:
       Parent() { cout << "Parents 생성" << endl; };
       ~Parent() { cout << "Parents 소멸" << endl; };
       void drink() { cout << "맥주를 마신다." << endl; };

      

       childSPtr kid;
};

 

class Child
{
   public:
       Child() { cout << "Child 생성" << endl; };
       ~Child() { cout << "Child 소멸" << endl; };
       void drink();

 

       parentWPtr father;
};

 

void Child::drink()
{
    cout << "우유를 마신다." << endl;
    parentSPtr f = father.lock();
// shared_ptr을 얻는다.
 
    if(f) // 객체가 존재하는지 반드시 체크해야 한다.
        f->drink();
}

 

int main(void)
{
    Child* child = new Child();

    parentSPtr pSptr(new Parent);
    childSPtr cSptr(child);

 

    cout << "#parent의 참조개수: " << pSptr.use_count() << endl;
    cout << "#child의 참조개수: " << cSptr.use_count() << endl;

 

    pSptr->kid = cSptr;
    cSptr->father = pSptr;

    cout << "#parent의 참조개수: " << pSptr.use_count() << endl;
    cout << "#child의 참조개수: " << cSptr.use_count() << endl;

 

    cSptr->drink();
    pSptr.reset();

 

    return 0;
}

 

==================================================================================================

 

 

4. intrusive_ptr

    : 참조 개수를 스마트 포인터 안에 두지 않고, 관리할 클래스 안에 보관하는 방식의 스마트 포인터

 

    : intrusive_ptr은 shared_ptr과 마찬가지로 참조 카운팅 방식으로 객체의 소멸 시점을 결정한다.

      하지만 intrusive_ptr의 경우 관리될 객체가 스스로 자신의 내부에 참조개수를 유지, 관리하도록

      요구한다.

     

    : intrusive_ptr 인스턴스가 새롭게 생성될 때는 intrusive_ptr_add_ref() 함수를 자동으로 호출하여

      참조의 개수를 증가시키도록 요구하고, 반대로 intrusive_ptr 인스턴스가 삭제 때 

      intrusive_ptr_release() 함수를 호출하여, 참조개수가 0일 될 때, 객체를 삭제하도록 요청한다.

      ( intrusive_ptr_add_ref()와 intrusive_ptr_release() 함수의 경우 사용자가 직접 구현해야 하며, 

        이 때 함수의 인자로 객체의 포인터를 넘겨줍니다. )

 

    : intrusive_ptr은 암묵적 형변환을 허용한다.

     

    [사용해야 하는 이유]

    - 기존의 독자적으로 참조개수를 가지는 객체에 스마트 포인터를 적용하기 위해서 필요

    - shared_ptr에 비해 작은 메모리 공간을 차지.

      (일반 포인터와 마찬가지로 포인터(T*) 형태로 대입 가능!)

   

--------------------------------------------------------------------------------------------------

 

[예제]

 

 

 

[ intrusivePtrTest.h ]

#include <iostream>
#include <memory>
#include <vector>
#include <boost/intrusive_ptr.hpp>
#include <boost/shared_ptr.hpp>

 

using namespace std;

 

class SharedObject
{
   public:
       SharedObject() : ref_count(0) { cout << "생성됨" << endl; };
       ~SharedObject() { cout << "소멸됨" << endl; };
       void Hello();
       int AddRef();
       int Release();

 

   private:
       int ref_count;

};

 

void intrusive_ptr_add_ref(SharedObject* ptr);
void intrusive_ptr_release(SharedObject* ptr); 

 

 

[ intrusivePtrTest.cpp ]

#include "intrusivePtrTest.h"

 

void SharedObject::Hello()
{
    cout << "Hello!" << endl;
}

 

int SharedObject::AddRef()
{

    // 참조 카운팅 증가시 SharedObject 안에 있는 ref_count 변수를 증가한다.
    return ++ref_count; 
// 반환

}

 

int SharedObject::Release()
{

    // 참조 카운팅 감소시 SharedObject 안에 있는 ref_count 변수를 감소한다.
    if(--ref_count == 0)  
// 참조 카운팅이 0 이면       

        delete this;         // 객체 소멸

 

    return ref_count;     // 참조 카운팅이 0보다 크다면 반환

 

int main(void)
{
    typedef boost::intrusive_ptr<SharedObject> iPtr;

    iPtr p = new SharedObject();
    vector<iPtr> vec;

 

    vec.push_back(iPtr(new SharedObject));
    vec.push_back(iPtr(new SharedObject));
    vec.push_back(iPtr(new SharedObject));
    vec.push_back(p);

 

    p->Hello();
 
    return 0;
}

 

void intrusive_ptr_add_ref(SharedObject* ptr) // intrusive_ptr이 생성될 경우
{
    ptr->AddRef();
}

 

void intrusive_ptr_release(SharedObject* ptr) // intrusive_ptr이 소멸될 경우
{
    ptr->Release();
}

 

==================================================================================================

 

 

5. shared_array

    : 동적 배열 포인터를 보관, 유지하는 스마트 포인터

    : 기능면에서는 shared_ptr과 유사 (배열에 대해 동작)

    : 배열처럼 처리 가능

 

--------------------------------------------------------------------------------------------------

 

[예제]

 

 

 

[ SharedArrayTest.h ]

#include <iostream>
#include <memory>
#include <boost/shared_array.hpp>

 

using std::cout;
using std::endl;

 

class Sample
{
   public:
       Sample() { cout << "생성" << endl; }
       ~Sample() { cout << "소멸" << endl; }
       void print() { cout << "print" << endl; }
};

 

 

[ SharedArrayTest.cpp ]

#include "SharedArrayTest.h"

 

int main(void)
{
    boost::shared_array<Sample> samplePtr(new Sample[5]);
// 동적으로 할당된 배열일 경우 사용

   

    samplePtr[2].print(); // 배열처럼 사용이 가능하다.

   

    return 0;

 

 

==================================================================================================

 

 

6. scoped_ptr 

    : 동적으로 할당된 객체의 포인터를 가지며, 유효범위를 벗어나 자신이 삭제 될 때,

      가리키는 객체를 자동으로 삭제하는 스마트 포인터 (가리키는 객체에 대한 삭제를 자동으로 수행)

 

    : auto_ptr과 달리 가지는 포인터를 다른 변수에 복사하거나 할당할 수 없으며,

      소유권 역시 이전할 수 없다.

 

    : STL의 컨테이너의 항목으로는 사용할 수 없다는 약점이 있다. → shared_ptr을 사용하는 것이 좋다.

    : scoped_array : scoped_ptr과 유사. 동적으로 생성한 객체 배열을 사용할 때 사용!

 

--------------------------------------------------------------------------------------------------

 

[예제]

 

 

 

[ ScopedPtrTest.h ]

#include <iostream>
#include <memory>
#include <boost/scoped_ptr.hpp>

 

using std::cout;
using std::endl;

 

class Sample
{
   public:
       Sample() { cout << "생성" << endl; }
       ~Sample() { cout << "소멸" << endl; }
       void print() { cout << "print" << endl; }
};

 

 

[ ScopedPtrTest.cpp ]

#include "ScopedPtrTest.h"

 

typedef boost::scoped_ptr<Sample> scopePtr;

 

void printStr(scopePtr* Ptr); // 값 복사가 불가능하므로 포인터 형태로 넘겨야 한다.

 

int main(void)
{
    scopePtr p1(new Sample());
    scopePtr p2(new Sample());

   

    p1->print();
    printStr(&p1);
// 스마트 포인터 scoped_ptr(scopePtr)의 포인터를 넘긴다.

 

   /*
       // scoped_ptr - 자신이 가지는 포인터를 다른 인스턴스로 할당하거나 넘겨줄 수 없다.
       //                    한번 가리키는 객체에 대한 삭제의 책임을 전적으로 진다.

       //                    (복사가 빈번한 STL에서 사용할 수 없다.)

   

    p2 = p1             // 에러! 할당 불가!
    scopePtr p3(p1) // 에러! 
  
*/
   

   return 0;
}

 

void printStr(scopePtr* Ptr) // 값 복사가 불가능하므로 call by reference(address)로 인자를 넘긴다.
{
    (*Ptr)->print();
}

 

==================================================================================================