[C/C++ 언어일반] C/C++ 스마트 포인터 관련

 

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

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

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

 

 

출처: http://yucherrypl.tistory.com/8848

1. delete를 직접 하지말고 자원관리객체에게 맡겨라

T *p = new T;
위의 코드를 다음으로 바꿉니다.
std::tr1::shared_ptr<T> p( new T );

자원을 획득하자마자 자원관리객체의 초기화 코드로 넘기는데 이를 자원획득즉초기화(RAII) 라 합니다.

2. delete[] 와 shared_ptr

 shared_ptr 의 생성자는 타입T 의 포인터, 즉 T* 만을 받는 생성자가 있고 T* 와 삭제자(deleter) 를 받는 생성자도 있습니다.
 삭제자를 넘겨주지 않는 첫 번째의 생성자같은 경우 shared_ptr 는 자신이 소멸할 때 자원에 delete 를 적용해줍니다. 근데 만약 아래처럼

std::tr1::shared_ptr<T> p( new T[10] );

동적 배열 자원을 넘겨주게 되면 new[] 로 할당된 자원을 delete[] 가 아닌 delete 로 반납을 행하는데 이런 경우오작동 또는 메모리 누수가 발생하게 됩니다. 이를 막기위해 두 가지 방식을 취할 수 있습니다.

첫 번째는 vector 와 같은 컨테이너로 여러 shared_ptr 객체를 이용하는 방법입니다.

std::vector< std::tr1::shared_ptr<T> > v( 10 );
v[0] = std::tr1::shared_ptr<T>( new T );
...

두 번째는 delete[] 를 이용하는 삭제자를 정의( 일종의 함수객체 ) 하고 shared_ptr 의 생성자로 넘겨주는 방법입니다.

struct arrayDeleter
{
  template< typename T >
  void operator()( T *p )
  {
     delete[] p;
  }
};

std::tr1::shared_ptr<T> p( new T[10], arrayDeleter() );

이렇게 하면 정상적으로 자원의 할당 / 반납이 행해집니다.

세 번째는 shared_array 사용

3. 삭제자의 다양한 활용

int a = 0;
std::tr1::shared_ptr<int> p( &a );

어떤 변수를 포인터로 가리키는 경우는 자주 볼 수 있습니다. 그와 비슷하게 shared_ptr 로 지역변수 a 를 가리키도록 한 것이 위 의 코드입니다. 그러나, 객체 p가 소멸할 때 &a 에 delete 를 적용하기 때문에 결과적으로 delete &a; 동작이 소멸 과정에서 행해집니다. 여기서 a 는 동적 자원이 아닌 지역변수이기 때문에 에러가 발생하게 됩니다. 이를 막기위해 소멸과정에서 어떠한 동작도 행하지 않도록 빈 삭제자( Empty deleter ) 를 지정할 수 있습니다.

struct emptyDeleter
{
  template< typename T >
  void operator()( T *p ){}
};

int a = 0;
std::tr1::shared_ptr<int> p( &a, emptyDeleter() );

이 번에는 C버전 파일입출력 라이브러리에서 fclose() 함수 호출을 shared_ptr 를 이용하여 자동으로 처리되도록 하는 예입니다.

struct fileCloser
{
  void operator( FILE *f )
  {
    if( f ){ fclose( f ); }
  }
};

std::tr1::shared_ptr<FILE> f( fopen( "test.txt", "rb" ), fileCloser() );

4. shared_ptr<T> <-> T*

가끔 shared_ptr 객체가 관리하는 자원을 얻어내야 하는 경우가 있는데 그 땐 shared_ptr<T>::get() 멤법함수를 이용합니다.

std::tr1::shared_ptr<T> p( new T );
T *ptr = p.get();  // p.get() 은 T* 를 반환합니다.

5. 자원의 교체

shared_ptr 객체가 관리하고 있는 자원을 다른 자원 또는 NULL 로 만들어야 하는 경우는 shared_ptr<T>::reset() 멤버함수를 이용합니다.

std::tr1::shared_ptr<T> p( new T );
p.reset();        // 이제 p는 자원을 관리하지 않습니다. 기존의 자원은 안전하게 delete 됩니다.
p.reset( new T );     // p는 새로운 자원을 관리합니다.
p = std::tr1::shared_ptr<T>();   // 빈 shared_ptr 객체를 대입합니다. p.reset(); 과 동일합니다.

6. 복사와 참조 카운팅

shared_ptr 객체는 복사될 때마다 자원을 복사하는게 아닌 자원의 참조개수를 증가시킵니다.

std::tr1::shared_ptr<int> p( new int );   // 자원은 p에 의해 1번 참조됩니다.
std::tr1::shared_ptr<int> p2( p );    // 자원은 p, p2에 의해 2번 참조됩니다.
p.reset();                 // 자원은 p2에 의해 1번 참조됩니다.
p = p2;                // 자원은 p, p2에 의해 2번 참조됩니다.
p2.reset();               // 자원은 p에 의해 1번 참조됩니다.
p.reset();                 // 자원은 더 이상 참조되지 않음으로 delete 됩니다.

7. 조건문과 shared_ptr

기존 포인터를 이용할 때 유효한 포인터인지 확인하기 위해

int *p = &a;
...
if( p != NULL )
{ ... }

위의 코드처럼 if 를 이용하여 NULL 과 비교하곤 했습니다. 이를 shared_ptr 에서는 다음처럼 작성합니다.

std::tr1::shared_ptr<int> p( new int );
...
if( !p )
{ ... }

위에서 !p 처럼 이용하면 됩니다. 즉 shared_ptr 객체는 bool 식으로 변환될 수 있습니다.

8. const 자원과 shared_ptr

상수 자원을 관리하거나 자원을 상수화시키기 위해 const 를 이용할 수 있습니다.

std::tr1::shared_ptr<int> p( new int );    // p객체는 언제든지 자원을 변경할 수 있습니다.
std::tr1::shared_ptr<int const> q( new int ); // q객체는 자원을 읽을수는 있으나 변경할 수는 없습니다.

p = q;                   // 자원을 변경하지 못하는 q객체를 자원이 변경가능한 p에 복사합니다.
                     // 그러나 이 코드는 컴파일되지 않습니다.

q = p;                   // 자원이 변경가능한 p객체를 자원을 변경하지 못하는 q객체에 복사합니다.
                     // 컴파일이 잘 됩니다.

기존 포인터에서 적용되던 상수성을 그대로 유지해줍니다.

9. 상속과 shared_ptr

Child 클래스가 Parent 클래스를 public 상속했다고 할 때 기존 포인터를 이용하여

Parent *p = new Child;

위와 같이 작성할 수 있습니다. 이를 shared_ptr 로 작성하면 다음과 같습니다.

std::tr1::shared_ptr<Parent> p( new Child );

다형성을 이용하는 곳에도 별다른 지장없이 shared_ptr 를 이용할 수 있습니다.

10. typedef 활용

shared_ptr 를 이용하면 타입이름이 너무 길어지게 되는데 여기에 typedef 를 활용합니다.

class CMyClass;
typedef std::tr1::shared_ptr<CMyClass> CMyClass_ptr;

class CMyClass
{
...
  CMyClass_ptr getThis(){ return CMyClass_ptr( this, emptyDeleter() ); }
...
};

위처럼 std::tr1::shared_ptr<T> 타입을 T_ptr 로 typedef 하는 방법입니다.

this 포인터를 넘기는 또다른 방법
boost::enable_shared_from_this 를 사용(http://naiades.tistory.com/tag/enable_shared_from_this)

11. 순환참조 문제

사람 클래스가 있고 멤버로 친구가 있다고 하겠습니다.

struct person
{
  std::string name;
  std::tr1::shared_ptr<person> myFriend;

  person( const std::string &_name ) : name( _name )
  {
    std::cout << name << "태어나다." << std::endl;
  }
  ~person()
  {
    std::cout << name << "죽다." << std::endl;
  }
};
그리고 아래와 같은 블럭이 실행되면 생성자는 제대로 호출되지만 소멸자가 호출되지 않는 문제가 발생합니다.

{
   std::tr1::shared_ptr<person> kim( new person( "kim" ) );
   std::tr1::shared_ptr<person> lee( new person( "lee" ) );

   kim->myFriend = lee;
   lee->myFriend = kim;
}

 원인을 알아보겠습니다. 블럭의 끝에서 lee 객체가 먼저 소멸하는데 참조개수가 둘( 자신과 kim의 친구로) 이므로 참조개수를 하나 줄이고 소멸은 끝납니다. 즉 lee 의 참조개수는 1이고 kim의 참조개수는 2입니다.
 다음 kim 객체가 소멸하는데 마찬가지로 참조개수가 둘( 자신과 lee의 친구로 ) 이므로 참조개수를 하나 줄이고 소멸이 끝납니다. 결과적으로.. kim, lee 둘 다 참조개수가 1인 상태로 남아있게 되는 문제가 발생했습니다.

 이렇게 다른 객체끼리 서로 참조를 갖는 형태를 순환참조라 하며 shared_ptr 는 순환참조시 제대로 자원해제를 하지 못합니다.
 이런 경우는 친구를 shared_ptr<person> 타입이 아닌 person* 타입으로 바꾸거나 빈 삭제자를 이용하거나 또 다른 스마트포인터중 하나인 weak_ptr 를 이용하여 해결할 수 있습니다. person* 타입으로 바꾸는 것은 쉽기 때문에 생략하고

먼저 빈 삭제자를 이용하는 방법을 말씀드리겠습니다.

아 래의 코드 대신

kim->myFriend = lee;
lee->myFriend = kim;

다음의 코드를 이용합니다.

lee->myFriend.reset( kim.get(), emptyDeleter() );
kim->myFriend.reset( lee.get(), emptyDeleter() );

이제 정상적으로 소멸되는 것을 확인할 수 있습니다.(두번 삭제 방지)

이 번엔 weak_ptr 을 이용해보겠습니다. 그 전에 weak_ptr 의 특징은 shared_ptr 객체를 받을 수 있으며 자원의 참조개수에 변화를 주지 않습니다. 그렇기 때문에 이런 순환문제에도 적절하게 동작하게 됩니다.

person 의 멤버변수를 아래의 코드 대신

std::tr1::shared_ptr<person> myFriend;

다음의 코드를 이용합니다.

std::tr1::weak_ptr<person> myFriend;

나머지 코드는 동일합니다. 마찬가지로 정상적으로 소멸되는 것을 확인할 수 있습니다.

출처: http://cafe.naver.com/jcga.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=1614
출처: http://yucherrypl.tistory.com/8848 [보물단지]

 

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

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

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

 

 

출처: https://msdn.microsoft.com/ko-kr/library/hh279674.aspx

 

스마트 포인터(최신 C++)

Visual Studio 2015

다른 버전

 

 

 

Visual Studio 2017 RC에 대한 최신 설명서는 Visual Studio 2017 RC 설명서를 참조하세요.

현대적인 C++ 프로그래밍에서 표준 라이브러리에는 프로그램이 메모리 및 리소스 누출로부터 방해 받지 않고 예외가 발생하지 않도록 보장하기 위해 사용되는 스마트 포인터가 포함됩니다.

스마트 포인터 용도

 

스마트 포인터는 <memory> 헤더 파일의 std 네임스페이스에 정의됩니다. RAII 또는 Resource Acquisition Is Initialialization 프로그래밍 언어에 중요합니다. 이 관용구의 주요 목표는 개체의 모든 자원 생성이 한 줄의 코드에서 만들어지고 준비되어 그 개체가 초기화되는 동시에 자원 수집이 발생하는 것을 확인하는 것입니다. 실제로 RAII의 기본 원칙은 힙 할당 리소스(예: 동적 할당 메모리 또는 시스템 개체 핸들)의 소유권을 해당 소멸자가 리소스를 삭제하거나 비우는 코드 및 모든 관련 정리 코드가 포함된 스택 할당 개체에 제공하는 것입니다.

대부분의 경우 실제 리소스를 가리키도록 기본 포인터 또는 리소스 핸들을 초기화할 때는 포인터를 스마트 포인터로 즉시 전달합니다. 현대적인 C++에서 기본 포인터는 성능이 중요하며, 소유권 관련 혼동 여지가 없는 제한된 범위, 루프 또는 지원 함수의 작은 코드 블록에서만 사용됩니다.

다음 예제에서는 원시 포인터 선언을 스마트 포인터 선언과 비교합니다.

C++

 

void UseRawPointer() { // Using a raw pointer -- not recommended. Song* pSong = new Song(L"Nothing on You", L"Bruno Mars"); // Use pSong... // Don't forget to delete! delete pSong; } void UseSmartPointer() { // Declare a smart pointer on stack and pass it the raw pointer. unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars")); // Use song2... wstring s = song2->duration_; //... } // song2 is deleted automatically here.

예제에서와 같이 스마트 포인터는 스택에서 선언되고 힙 할당 개체를 가리키는 원시 포인터를 사용하여 초기화하는 스마트 포인터입니다. 스마트 포인터가 초기화되면 원시 포인터를 소유합니다. 원시 포인터가 지정한 메모리를 스마트 포인터가 삭제해야 함을 의미합니다. 스마트 포인터 소멸자에는 삭제할 호출이 포함되며, 스마트 포인터는 스택에 선언되기 때문에 스마트 포인터가 범위를 벗어나면 스택의 다른 위치에서 예외가 throw되더라도 해당 소멸자가 호출됩니다.

스마트 포인터 클래스 오버로드가 캡슐화된 원시 포인터를 반환하는 익숙한 포인터 연산자인 -> 및 *를 사용하여 캡슐화된 포인터에 액세스합니다.

C++ 스마트 포인터 관용구는 C# 같은 언어에서의 개체 생성과 유사합니다. 즉 개체를 생성한 후 시스템이 정확한 시간에 그것을 지울 수 있도록 합니다. 백그라운드에서 실행된 별도 가비지 수집기는 런타임 환경을 빠르고 효율적이게 만드는 표준 C++ 범위 지정 규칙을 통해 관리되는 메모리라는 것이 차이점입니다.

 중요

매개 변수 목록이 아닌 별도 코드 줄에서 스마트 포인터를 만들어야 특정 매개 변수 목록 할당 규칙으로 인해 아주 작은 리소스 누수도 발생하지 않습니다.

다음 예제에서는 표준 템플릿 라이브러리의 unique_ptr 스마트 포인터 형식이 큰 개체에 대한 포인터를 캡슐화하는 데 어떻게 사용될 수 있는지 보여줍니다.

C++

 

class LargeObject { public: void DoSomething(){} }; void ProcessLargeObject(const LargeObject& lo){} void SmartPointerDemo() { // Create the object and pass it to a smart pointer std::unique_ptr<LargeObject> pLarge(new LargeObject()); //Call a method on the object pLarge->DoSomething(); // Pass a reference to a method. ProcessLargeObject(*pLarge); } //pLarge is deleted automatically when function block goes out of scope.

이 예제는 다음과 같이 스마트 포인터를 사용하는 필수 단계를 보여 줍니다.

  1. 스마트 포인터를 자동(지역) 변수로 선언합니다. (스마트 포인터 자체에 new 또는 malloc 식을 사용하지 마십시오.)
  2. 형식 매개 변수에서 캡슐화된 포인터가 가리키는 대상의 형식을 지정합니다.
  3. 원시 포인터를 스마트 포인터 생성자의 new-ed 개체에 전달합니다. (일부 유틸리티 기능 또는 스마트 포인터 생성자로 이 작업을 자동으로 수행할 수 있습니다.)
  4. 오버로드된 -> 및 * 연산자를 사용하여 개체에 액세스합니다.
  5. 스마트 포인터로 개체를 삭제할 수 있습니다.

스마트 포인터는 메모리 및 성능 관점에서 최대한 효율적으로 사용할 수 있도록 설계되었습니다. 예를 들어, unique_ptr의 유일한 데이터 멤버는 캡슐화된 포인터입니다. 즉, unique_ptr은 해당 포인터와 정확히 동일한 크기(4바이트 또는 8바이트)입니다. 스마트 포인터 오버로드된 * 및 -> 연산자를 사용하여 캡슐화된 포인터에 액세스하면 원시 포인터에 직접 액세스하는 것보다 크게 느려지지 않습니다.

스마트 포인터에는 "점" 표기법을 사용하여 액세스할 수 있는 고유한 멤버 함수가 있습니다. 예를 들어, 일부 STL 스마트 포인터에는 포인터 소유권을 릴리스하는 멤버 다시 설정 기능이 있습니다. 다음 예에 표시된 것처럼 스마트 포인터가 범위를 벗어나기 전에 스마트 포인터에서 소유하는 메모리를 비우려는 경우에 유용합니다.

C++

 

void SmartPointerDemo2() { // Create the object and pass it to a smart pointer std::unique_ptr<LargeObject> pLarge(new LargeObject()); //Call a method on the object pLarge->DoSomething(); // Free the memory before we exit function block. pLarge.reset(); // Do some other work... }

스마트 포인터는 일반적으로 원시 포인터를 직접 액세스하는 방법을 제공합니다. STL 스마트 포인터에는 이 목적의 get 멤버 함수가 포함되며, CComPtr에는 공용 p 클래스 멤버가 포함됩니다. 기본 포인터에 대한 직접 액세스를 제공하면 스마트 포인터를 사용하여 자체 코드에서 메모리를 관리하고 스마트 포인터를 지원하지 않는 코드에 원시 포인터를 전달할 수 있습니다.

C++

 

void SmartPointerDemo4() { // Create the object and pass it to a smart pointer std::unique_ptr<LargeObject> pLarge(new LargeObject()); //Call a method on the object pLarge->DoSomething(); // Pass raw pointer to a legacy API LegacyLargeObjectFunction(pLarge.get()); }

스마트 포인터의 종류

 

다음 섹션에서는 다양한 종류의 Windows 프로그래밍 환경에서 사용할 수 있는 스마트 포인터에 대한 정보를 요약하고 사용하는 경우에 대해 설명합니다.

C++ 표준 라이브러리 스마트 포인터
POCO(Plain Old C++ 개체)에 대한 포인터를 캡슐화하는 데 가장 먼저 스마트 포인터를 사용합니다.

  • unique_ptr
    기본 포인터로 한 명의 소유자만 허용합니다. shared_ptr이 필요하다는 점을 확실히 알 경우 POCO의 기본 선택으로 사용합니다. 새 소유자로 이동할 수 있지만 복사하거나 공유할 수 없습니다. 사용하지 않는 auto_ptr을 대체합니다. boost::scoped_ptr과 비교합니다. unique_ptr은 작고 효율적이며, 크기는 1 포인터이고 STL 컬렉션에서 빠른 삽입 및 검색을 위해 rvalue 참조를 지원합니다. 헤더 파일: <memory>. 자세한 내용은 방법: unique_ptr 인스턴스 만들기 및 사용 및 unique_ptr 클래스를 참조하십시오.
  • shared_ptr
    참조 횟수가 계산되는 스마트 포인터입니다. 원시 포인터 하나를 여러 소유자에게 할당하려고 할 경우 사용합니다(예: 컨테이너에서 포인터 복사본을 반환할 때 원본을 유지하고 싶을 경우). 원시 포인터는 모든 shared_ptr 소유자가 범위를 벗어나거나 소유권을 포기할 때까지 삭제되지 않습니다. 크기는 2개의 포인터입니다. 하나는 개체용이고, 다른 하나는 참조 횟수가 포함된 공유 제어 블록용입니다. 헤더 파일: <memory>. 자세한 내용은 방법: shared_ptr 인스턴스 만들기 및 사용 및 shared_ptr 클래스를 참조하십시오.
  • weak_ptr
    shared_ptr과 함께 사용할 수 있는 특별한 경우의 스마트 포인터입니다. weak_ptr은 하나 이상의 shared_ptr 인스턴스가 소유하는 개체에 대한 액세스를 제공하지만, 참조 수 계산에 참가하지 않습니다. 개체를 관찰하는 동시에 해당 개체를 활성 상태로 유지하지 않으려는 경우 사용합니다. shared_ptr 인스턴스 사이의 순환 참조를 차단하기 위해 필요한 경우도 있습니다. 헤더 파일: <memory>. 자세한 내용은 방법: weak_ptr 인스턴스 만들기 및 사용 및 weak_ptr 클래스를 참조하십시오.

COM 개체의 스마트 포인터(Windows 기본 프로그래밍)
COM 개체를 사용하는 경우 인터페이스 포인터를 적절한 스마트 포인터 형식으로 래핑합니다. ATL(Active Template Library)은 다양한 목적을 위해 여러 스마트 포인터를 정의합니다. .tlb 파일에서 래퍼 클래스를 만들 때 컴파일러가 사용하는 _com_ptr_t 스마트 포인터 형식을 사용할 수도 있습니다. ATL 헤더 파일을 포함하지 않으려는 경우에 가장 좋습니다.

CComPtr Class
ATL을 사용할 수 없는 이상 이 형식을 사용합니다. AddRef 및 Release 메서드를 사용하여 참조 수 계산을 수행합니다. 자세한 내용은 방법: CComPtr 및 CComQIPtr 인스턴스 만들기 및 사용을 참조하십시오.

CComQIPtr Class
CComPtr과 유사하지만 COM 개체에서 QueryInterface를 호출하는 간단한 구문을 제공합니다. 자세한 내용은 방법: CComPtr 및 CComQIPtr 인스턴스 만들기 및 사용을 참조하십시오.

CComHeapPtr Class
CoTaskMemFree를 사용하여 메모리를 해제하는 개체에 대한 스마트 포인터

CComGITPtr Class
GIT(전역 인터페이스 테이블)에서 가져온 인터페이스에 대한 스마트 포인터입니다.

_com_ptr_t 클래스
기능 면에서 CComQIPtr과 유사하지만 ATL 헤더에 의존하지 않습니다.

POCO 개체에 대한 ATL 스마트 포인터
ATL은 COM 개체에 대한 스마트 포인터뿐만 아니라 이전 일반 C++ 개체에 대한 스마트 포인터 및 스마트 포인터 컬렉션을 정의합니다. 클래식 Windows 프로그래밍에서 이러한 형식은 특히 코드 이식이 필요하지 않거나 STL 및 ATL 프로그래밍 모델을 혼합하지 않으려는 경우에 STL 컬렉션에 대한 유용한 대안이 될 수 있습니다.

CAutoPtr Class
복사 소유권을 전송하여 고유 소유권을 적용하는 스마트 포인터입니다. 사용되지 않는 std::auto_ptr 클래스에 비교할 수 있습니다.

CHeapPtr Class
malloc 함수를 사용하여 할당된 개체의 스마트 포인터입니다.

CAutoVectorPtr Class
배열을 위해 new[]를 사용하여 할당하는 스마트 포인터입니다.

CAutoPtrArray Class
CAutoPtr 요소 배열을 캡슐화하는 클래스입니다.

CAutoPtrList Class
CAutoPtr 노드 목록을 조작하기 위해 메서드를 캡슐화하는 클래스입니다.

참고 항목

 

C++의 진화
C++ 언어 참조
C++ 표준 라이브러리
(NOTINBUILD)Overview: Memory Management in C++

 

 

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

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

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

 

 

출처: http://ozt88.tistory.com/28

공유하는 자원을 사용할 때 std::shared_ptr

std::unique_ptr은 사용권을 양도하는 식으로 자원을 관리했다. 하지만 실제로 우리가 할당된 자원(포인터)을 사용할 때 unique한 방식보다는 여러 곳에서 그 자원을 공유하는 방식으로 사용하게 된다. 하지만 처음 할당한 자원을 여기저기에서 참조하고 있으면 dangling 포인터와 같은 자원관리 문제가 발생하기 쉽다. Java에는 Garbage Collection이라는 자동 자원관리 방식을 지원해서 공유된 자원에 유저가 신경쓸 필요없이 편하게 프로그래밍이 가능하다. 대신 유저가 자원이 해제되는 시점에 대해서 알기 어렵다는 단점이 있다. C++ 11부터는 shared_ptr을 사용하여 두가지 목표(자동 자원관리, 예측 가능한 해제시점)를 모두 달성할 수 있게 해준다.

shared_ptr의 동작 방식

shared_ptr은 이름처럼 할당된 자원을 소유하지 않고 그저 가리키고 있는 포인터이다. 대신 자원이 더 이상 필요하지 않은 적절한 시점을 파악하고 알아서 자원을 해제해주는 방식으로 자원을 관리한다. garbage collection의 경우 내부적인 복잡한 규칙에 따라서 이 시점을 찾아 해제하지만, share_ptr은 참조횟수를 추적하여 참조횟수가 0이 되면 삭제하는 단순한 룰로 동작한다.(대신 여러 민감한 이슈들(순환 참조등)은 유저가 알아서 처리해야하겠지만...)

shared_ptr이 참조 횟수를 관리하는 방법은 다음과 같다. shared_ptr의 생성자(default or 복사)가 호출되면 가리키는 object의 참조 횟수를 증가시킨다. 소멸자가 호출되면 가리키는 object의 참조횟수를 감소시킨다. 복사 할당자가 호출되면, sp1 = sp2; 이 상황에서 sp1가 가리키던 object의 참조 횟수는 감소되고 sp2가 가리키는 object의 참조 횟수는 증가한다. move 생성/할당에 대해서는 기존의 포인터를 제거하고 그대로 이전하는 것이므로 참조횟수를 변경시키지 않는다. 참조 횟수 증가 감소는 atomic한 연산으로 동작해야한다. 참조횟수가 0이 되는 순간 자원을 해제해야하는데 참조횟수를 증가하고 감소시키는 연산이 경쟁상태에 돌입하게 된다면, shared_ptr이 가리키는 객체의 상태가 undefine될 것이기 때문이다.

앞에서 말한것처럼 shared_ptr은 일반 raw pointer와 달리 참조 횟수를 관리해야한다. 참조 횟수는 같은 객체를 가르키는 shared_ptr끼리 공유하는 자원이기 때문에 참조 횟수를 관리하는 객체는 하나의 shared_ptr에 있을 수는 없다. 그래서 shared_ptr이 다루는 참조 횟수는 별개로 할당된 객체에게 관리되어야한다. 참조 횟수를 관리하는 객체의 자원도 참조횟수가 0이 되면 share_ptr이 가리키는 객체의 자원과 함께 해제될 것이므로 자원관리의 문제는 없다. 어쨌든 shared_ptr은 가리키는 대상의 주소 뿐만아니라 참조 횟수를 관리하는 객체의 주소도 가지고 있어야한다. 그래서 가리키는 객체의 포인터 + 참조횟수 관리 객체 포인터 해서 2배의 크기를 갖는다. (그래봤자 8바이트 in 32bit 지만)

정확히 말하면 참조 횟수'도' 관리하는 객체로 표현해야하겠다. 이 객체는 일반 참조 횟수뿐 아니라, weak 참조 횟수(item 21에서 설명), unique_ptr에서 봤던 custom deleter(item 18참조) 및 allocator 그리고 기타등등에 해당하는 정보를 가지고 있다. 그래서 shared_ptr은 unique_ptr 처럼 custom deleter에 따라서 새로운 타입을 만들어낼 필요가 없다.

auto loggingDel = [](Widget* pw) //Custom Deleter { makeLogEntry(pw); delete pw; } std::unique_ptr<Widget, delctype(loggingDel)> //deleter가 ptr 타입의 부분이된다. upw( new Widget, loggingDel); std::shared_ptr<Widget> spw( new Widget, loggingDel); //deleter랑 상관없는 ptr 타입

deleter가 달라도 타입이 같다는 것은 매우 큰 강점이다. 우선은 쓰기가 쉽고(^^), 같은 컨테이너(벡터, 리스트)에 같이 담을 수도 있고, 하나의 패러미터 타입에 전달할 수도 있다.

auto customDeleter1 = [](Widget* pw){...} //서로 다른 deleter 생성 auto customDeleter2 = [](Widget* pw){...} std::shared_ptr<Widget> pw1(new Widget, customDeleter1); std::shared_ptr<Widget> pw2(new Widget, customDeleter2); std::vector<std::shared_ptr<Widget>> vpw{pw1, pw2}; //같은 벡터에 담을수도 있고 void func(std::shared_ptr<Widget> arg); func(pw1); //함수의 인자로 똑같이 전달할 수 있다. func(pw2);

item18에서 말한것 처럼 만약 람다로 이루어진 deleter가 엄청나게 많은 state을 갖게 된 경우를 생각해보면, unique_ptr은 점점 무거워 질 것이다. 하지만 shared_ptr은 이런 deleter들을 따로 갖고 있는것이 아니라 control block에 저장해 두기 때문에, 용량이 늘어나지도 않는다.

참조 횟수를 관리하는 객체 Control Block

 

 

 

control block 덕분에 share_ptr은 쉽고 간단하게 사용할 수가 있었다. 실제로 control block은 share_ptr이 가리키는 객체의 자원관리에 있어서도 많은 역할을 한다. 사실상 shared_ptr이 아니라 이 객체가 할당한 자원을 관리한다고 봐도 무방할 것이다. 그렇다면 자원할당된 하나의 객체에 하나의 control block만 있는 것이 이상적이다. 이제 이 객체를 언제 생성하느냐에 대한 문제로 넘어가게 된다. 원칙적으로 shared_ptr에 의해 특정 객체가 처음 referencing 되었을 때 이 객체를 생성해야할 것이다. shared_ptr은 다음의 룰에 따라서 control block을 생성한다.

  • std::make_shared (item 21에서 자세히다룸)으로 shared_ptr을 생성할 때마다 control block이 생성된다. 이 함수는 아직 공유되지 않은 특정 자원(객체)을 공유하겠다고 선언하는 것과 같다. shared_ptr이 이 함수의 생성만으로 만들어지면 좋겠지만 현실은 그렇지 않기에 다음의 룰들이 추가된다.
  • unique한 소유권이 보장된 포인터(ex: unique_ptr, auto_ptr)로 부터 shared_ptr이 생성된 경우 control block이 생성된다. 위의 내용과 다르지 않다. 아직 공유되지 않았다는것이 확실하므로, 이제부터 공유 자원으로 등록하는 의미에서 control block을 새로 만들어 낸다.
  • raw pointer로 shared_ptr이 만들어졌을 때 control block을 생성한다. 이 조건에는 해당 raw pointer가 다른 shared_ptr 또는 weak_ptr(item 20에서 설명)에 의해서 따로 공유된 적이 없다는 전제가 깔려있다. 이미 공유된 raw pointer를 다시 새로운 control block으로 공유한다면, 같은 포인터에 대해서 여러개의 control block이 생기는 불상사가 발생할 것이다. 그러므로 이미 공유된 자원은 raw pointer가 아니라 반드시 shared_ptr 또는 weak_ptr로 다루어야 할 것이다.

중복된 Control Block을 피하는 방법

세 번째 룰은 매우 위태위태해 보인다. 이 룰에 의해서 같은 자원(raw 포인터)에 대해 다수의 control block이 생성된다면 같은 자원에대해 레퍼런스 카운터가 별도로 동작하고, 그래서 각각 0이 된 시점이 달라 별도로 해제하고, 그러면 댕글링 포인터문제가 발생할 수밖에 없다. 쉬운 예시를 통해 문제가 발생하는 경우를 알아보자.

//자원할당된 raw pointer auto pw = new Widget; ... //raw로 만드니 control block도 같이 할당 std::shared_ptr<Widget> spw1(pw, loggingDel); ... //raw로 만드니 control block도 같이 할당2 std::shared_ptr<Widget> spw2(pw, loggingDel);

위 예제만 보면 문제가 바로 보이지만, 중간에 코드가 길다면 누구나 쉽게 할 수 있는 실수이다. 그러니까 shared_ptr을 생성할때 가능하면 raw pointer를 쓰지 않는게 좋다. 가장 좋은 방법은 std::make_shared를 명시적으로 사용하는 것이다. 하지만 make_shared는 custom deleter를 지정할 수 없다는 문제가 있다. 위 예제에서 std::make_shared를 쓸 수 없는 이유가 이것이다. 그러면 같은 raw pointer를 다시 사용할 수 없게 만들어 버리는 방법도 있다.

//생성자를 사용해서 바로 shared_ptr을 만들자! std::shared_ptr<Widget> spw1(new Widget(), loggingDel); ... //raw pointer대신 shared_ptr로 생성 (컨트롤 블록 만들어지지 않는다.) std::shared_ptr<Widget> spw2(spw1);

shared_ptr에서 자원을 중복해서 공유하는 많은 케이스는 this 포인터를 shared_ptr로 만드는 경우에서 발생한다. this도 분명한 raw Pointer이므로 this로 shared_ptr을 만들면 새로운 control block이 생겨난다. 하지만 외부에서 해당 객체를 shared_ptr로 만드는 코드가 있다면 여기서 문제가 발생한다. 이것은 한눈에 알아보기 쉽지 않기때문에, 찾기 어려운 버그가 될 수 있다. 다음의 예제를 보면서 더 이야기를 해보겠다.

//작업 완료된 위젯들을 저장하는 벡터 std::vector<std::shared_ptr<Widget>> processedWidget; class Widget{ public: ... void process(){ ... //작업 끝나고 벡터에 this를 집어넣는다. //이러면 this가 shared_ptr<Widget>으로 변환되어 저장. //raw pointer로 shared_ptr을 생성하므로 control block이 할당 //이 함수 호출될 때마다 새로운 블럭이 생성되는 미친 코드 //한번만 호출되는 것이 보장된다고 해도 외부에서 이 객체를 shared_ptr로 만들면 문제발생 processedWidgets.emplace_back(this); } }

std::shared_ptr을 만든 사람들은 이런 문제에대한 예방책을 미리 마련해 두었다. 이름이 좀 길긴하지만, std::enable_shared_from_this를 사용하면 이 문제를 미연에 방지할 수 있다. std::enable_shared_from_this를 상속받은 클래스는, 그 이름처럼 this를 가지고 shared_ptr을 안전하게 만들 수 있는 멤버함수를 상속받는다. shared_from_this()를 호출하게 되면 this를 가지고 shared_ptr를 생성할때, 컨트롤 블록을 새로 생성하지 않고, 기존의 컨트롤 블록을 가진 새로운 shared_ptr을 생성하여 반환한다.

class Widget : public std::enable_shared_from_this<Widget> { public: ... void process() { ... //this의 shared_ptr을 받아서 사용가능!? processedWidgets.emplace_back(shared_from_this()); } }

이 해결방안에는 숨겨진 전제가 있다. this로 shared_ptr을 한번은 만들어서 컨트롤 블록을 생성해놔야 된다는 것이다. 만약 this에 대한 컨트롤 블록이 없다면 shared_from_this()함수는 예외를 throw하거나 정의되지 않은 동작을 수행할 것이다. 이런 이유에서 enable_shared_from_this를 상속받는 클래스는 보통 생성자 대신 생성자 역할을 하는 함수를 만들고 생성자를 호출한뒤에 shared_ptr로 만들어 컨트롤 블록등 미리 등록하는 방법을 사용한다.

class Widget : public std::enable_shared_from_this<Widget> { public: //생성자에게 perfect-forwarding을 하는 팩토리 함수 //그리고 this의 shared_ptr을 리턴하여 //인스턴스가 각각 반드시 하나의 컨트롤 블럭을 갖을 수 있도록 template<typename ... Ts> static std::share_ptr<Widgetr> create(Ts&& ... params); ... void process() { ... //완전 문제없이 this의 shared_ptr을 받아서 사용가능! processedWidgets.emplace_back(shared_from_this()); } private: Widget(); //생성자를 private로 만들어서 외부에서 접근불가능하게 만든다. }

shared_ptr은 비싸다?

지금까지 shared_ptr에 대한 여러 내용을 알아보았는데, shared_ptr을 운영하는데 필요한 자원이 적지 않아보인다. 우선 control block에 들어가는 수많은 정보를 저장하려면 많은 메모리가 사용될 것같다. 그리고 control block이 하는 일들을 구현하기 위한 atomic 연산들과 제대로 해제하기 위해서 가상함수들의 비용이 만만치 않아보인다. 하지만 scott meyers가 말하길 shared_ptr은 모든 자원관리에 대한 해답은 아니지만, 그것이 제공하는 편의에 대해서 매우 정직한, 이유있는 비용을 사용한다고 한다.

우선 default 소멸자나 할당자를 사용하는 shared_ptr을 make_shared로 할당하는 경우(대부분의 경우), control block은 3 word의 크기면 충분하고, 컨트롤 블럭을 할당하는데 드는 비용은 거의 공짜다.(이후 item 21에서 자세하게 설명한다). 그릐고 shared_ptr을 역참조하는데 드는 비용도 기존의 raw pointer와 동일하다. 참조 횟수를 관리하는 두개의 atomic 연산의 경우 분명 non-atomic한 연산보다는 비싸겠지만, 각각의 동작이 하나의 CPU 명령어(instruction)에 매핑되어서 매우 빠르게 동작할 수 있다. 가상함수의 경우 오직 소멸자 호출에서 한번씩만 사용되므로, shared_ptr 한개당 한번씩만 호출된다고 생각하면 그렇게 큰 비용이 든다고 할 수 없다.

이런 비용을 지불해서 자동적으로 관리되는 자원 체계를 얻는 것은 분명한 이득일 것이다. 대부분의 경우 shared_ptr을 사용하는 것이 직접 자원을 관리하는 것보다 훨씬 선호된다. 그렇다고 마냥 shared_ptr을 사용하라는 말은 아니다. 만약 공유되는 자원이 필요없는 경우라면 unique_ptr을 사용하는 것이 더 좋은 선택이 된다. unique_ptr의 비용은 거의 raw pointer와 다를 바 없기 때문이다. 그리고 unique_ptr은 shared_ptr로 변환가능하지만, 그 반대는 불가능하다.

unique_ptr과는 달리 shared_ptr은 배열을 다룰 수 없다. std::shared_ptr<T[]> 는 없는 타입이다. 물론 배열의 첫번째 포인터를 shared_ptr로 만들고 deleter를 delete[]로 설정하는 방법으로 사용할수도 있겠다. 하지만 shared_ptr은 []연산자를 지원하지 않기 때문에, 이런 배열을 사용하는 것은 이상한 문법이 될것이다. 대신 c++ 11에서 지원하는 배열타입들을 shared_ptr로 관리하는 것이 더욱 현명한 방법이 될 것이다.

 

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

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

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

 

 

출처: http://chipmaker.tistory.com/entry/uniqueptr-sharedptr-%EC%9D%BD%ED%9E%88%EA%B8%B0

unique_ptr, shared_ptr 읽히기

Program Lang./C++ 2016.12.30 14:45

1. 목적

c++ unique_ptr, shared_ptr 개인적인 정리

 

2. unique_ptr

Object에 대한 소유권 이전은 move만 가능하고 copy는 불가하다.

c++14부터 make_unique 함수를 통해서 unique_ptr 객체를 생성할 수 있다.

결론적으로 객체에 대해서 소유권을 독점하고 객체 사용이 끝나면 자동으로 객체 소멸자를 호출해 주는 Smart Pointer이다.

 

#include <memory>

 

std::unique_ptr<데이터 타입> 포인터변수 (new 클래스 또는 new 생성된 포인터)

std::unique_ptr<데이터 타입> 포인터변수 = std::make_unique<데이터타입>(생성자 매개변수)

-> std::make_unique는 c++17이후에서 가능

 

3. shared_ptr

Object에 대한 소유권을 여러 포인터가 공유할 수 있다. 하나의 객체를 공유하는 포인터들이 참조 카운터가 '0'이 되면 자동으로 객체의 소멸자를 호출해 준다.

쉽게 결론적으로 말하면 하나의 객체를 여러 포인터가 가르킬 수 있으며 객체를 가르키는 포인터가 더 이상 없으면 자동으로 객체에 대한 소멸자를 호출해 준다.

 

#include <memory>

 

std::shared_ptr<데이터 타입> 포인터변수 = std::make_shared<데이터타입>(생성자 매개변수);

std::shared_ptr<데이터 타입> 포인터변수 (new 클래스 또는 new 생성된 포인터);

 

4. 테스트 코드 (공통)

이름을 입력 받아서 rotate 시키는 class를 하나 선언

 

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <memory>
#include <string>


using namespace std;


class person
{
public:
    person();
    person(string str);
    virtual ~person();
    void rotate(void);
    string& get_string(void);
private:
    string m_str;
};


person::person() {
    cout << __FUNCTION__ << " called !!" << endl;
}


person::person(string str) : m_str(str) {
    cout << __FUNCTION__ << " called !!" << endl;
}


person::~person() {
    cout << __FUNCTION__ << " called !! " << endl;
}


void person::rotate(void) {
    //cout << __FUNCTION__ << " called !! " << endl;
         
    char s = m_str.at(0);
    m_str.replace(m_str.begin(), m_str.end(), m_str.begin()+1, m_str.end());
    m_str.insert(m_str.end(), s);
}


string& person::get_string(void) {
    //cout << __FUNCTION__ << " called !! " << endl;
    return m_str;
}

 

5. unique_ptr로 테스트하기

unique_ptr 선언하고 하나의 객체를 할당 받는 코드에 집중

 

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(int argc, char* argv[])
{
    unique_ptr<person> p( new person("heesoon.kim") );


    for(int i = 0; i < 11; i++)
    {
        p->rotate();
        cout << p->get_string() << endl;
    }


    unique_ptr<person> p2(std::move(p));
    cout << "p2: " << p2->get_string() << endl;


    //cout << p->get_string() << endl;


    return 0;
}

 

6. shared_ptr로 테스트 하기

shared_ptr를 하나 선언하고 객체를 할당하는 방법에 집중

 

?

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, char* argv[])
{
    shared_ptr<person> p = make_shared<person>("heesoon.kim");
     
    for(int i = 0; i < 11; i++)
    {
        p->rotate();
        cout << p->get_string() << endl;
    }


    return 0;
}

 

7. 실행 결과

unique_ptr 테스트에서 //cout << p->get_string() << endl; 주석을 제거하면 세그먼트 폴트로 죽는다.

소유권이 이미 p2로 이전되었기 때문에 p에서 객체를 호출하면 죽는다.

결론적으로 shared_ptr은 객체를 가르키는 포인터가 Copy되면서 여러개 공유할 수 있으며 내부적으로 객체를 가르키는 counter를 관리하지만 unique_ptr은 Copy가 아니라 소유권이 Move되는 개념으로 볼 수 있다.

개발 시에 이점을 유의해서 객체를 위한 스마트 포인터 사용을 해야 할 것으로 보인다.

 

 

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

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

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

 

 

출처: http://heurinbada.tistory.com/118

 

 

 

기존의 동적할당 포인터

- 메모리 누수 문제 발생가능 / 귀찮음

 

auto_ptr

- 배열할당시 메모리 누수 문제 발생

 

shared_ptr

#include <memory>

using namespace std;

 

shared_ptr<TEST> test_ptr(new TEST[3], default_delete<TEST [ ]>( ) );

                                   배열 동적할당        삭제자     삭제객체가 배열임을 알림

 

메모리관리 (참조수 증가와 감소)는 다음과같이 이루어진다

#include <iostream>

#include <memory>

using namespace std;

class TEST

{

public:

TEST(int n)

: m_test(n)

{

cout << "TEST" << m_test << "번 생성" << endl;

}

~TEST()

{

cout << "TEST" << m_test << "번 소멸" << endl;

}

int m_test;

};

 

int main()

{

shared_ptr<TEST> t1(new TEST(1)); //참조수 1

cout << "t1의 참조수 : " << t1.use_count() << endl;

 

{

shared_ptr<TEST> t2 = t1; //참조수 2

cout << "t1의 참조수 : " << t1.use_count() << endl;

cout << "t2의 참조수 : " << t2.use_count() << endl;

}

// t2는 스코프 벗어나며 파괴됨. t1 참조수 1

cout << "t1의 참조수 : " << t1.use_count() << endl;

 

return 0;

}

 

 

shared_ptr로 관리하는 객체 사용

 

포인터를 얻을 때 : get( )

참조를 얻을 때 : *

인스턴스 멤버에 접근 : -> 

#include <iostream>

#include <memory>

using namespace std;

class Particle

{

public:

Particle(int nID)

{

m_nID = nID;

cout << "Particle " << m_nID << "번 생성. " << endl;

}

~Particle()

{

cout << "Particle " << m_nID << "번 소멸. " << endl;

}

int m_nID;

};

 

 

int main()

{

shared_ptr<Particle> ParticlePtr(new Particle(10));

 

// Particle 인스턴스의 참조를 얻어서 직접 멤버 변수 사용

Particle& ParticleRef = *ParticlePtr;

cout << "ParticleRef의 ID: " << ParticleRef.m_nID << endl;

 

// Particle 인스턴스의 포인터를 얻어서 멤버 변수 사용

Particle* pParticle = ParticlePtr.get();

cout << "pParticle의 ID: " << pParticle->m_nID << endl;

 

// Particle1을 통해서 Particle 인스턴스의 멤버 접근

cout << "ParticlePtr의 ID: " << ParticlePtr->m_nID << endl;

 

return 0;

}

 

명시적으로 관리하던 객체를 삭제 및 바꾸기 : reset()

 

교체시 기존 객체는 파괴됨

또한 reset()멤버함수에 인자값을 사용하지않으면 명시적으로 객체를 파괴

 

ParticlePtr.get(); 했던것처럼

ParticlePtr.reset(); 

ParticlePtr.reset(new Particle(11)); 같은식으로 사용

 

객체교환 : swap

ParticlePtr1.swap(ParticlePtr2);

 

 

배열객체 사용 및 삭제자의 지정

 

#include <iostream>

#include <memory>

using namespace std;

class Particle

{

public:

Particle()

{

cout << "Particle 생성자" << endl;

}

~Particle()

{

cout << "Particle 소멸자" << endl;

}

};

 

int main()

{

{

shared_ptr<Particle> ParticlePtr1(new Particle,

[](Particle* pParticle) {cout << "Call Deleter: Particle1" << endl;delete pParticle;});

}

 

{

cout << "배열 객체" << endl;

shared_ptr<Particle> ParticlePtr2(new Particle[3],

[](Particle* pParticle) {cout << "Call Deleter: Particle2" << endl;delete[] pParticle;});

}

return 0;

}

위 코드처럼 삭제자를 직접 지정할 수 도 있다. (메모리풀 같은곳에 사용시 유용)

 

 

순환참조로 인한 메모리누수!!

#include <iostream>

#include <memory>

using namespace std;

class Particle

{

public:

Particle(int nID)

{

m_nID = nID;

cout << "Particle " << m_nID << "번 생성. " << endl;

}

~Particle()

{

}

int m_nID;

shared_ptr<Particle> m_Other;

};

int main()

{

shared_ptr<Particle> ParticlePtr1(new Particle(1));

{

shared_ptr<Particle> ParticlePtr2(new Particle(2));

ParticlePtr1->m_Other = ParticlePtr2;

ParticlePtr2->m_Other = ParticlePtr1;

cout << "ParticlePtr1의 ID: " << ParticlePtr1->m_nID << endl;

cout << "ParticlePtr2의 ID: " << ParticlePtr2->m_nID << endl;

}

cout << "ParticlePtr1의 ID: " << ParticlePtr1->m_nID << endl;

return 0;

}

다음과같은 경우 순환참조로인해 메모리누수가 발생한다.

좀처럼 하지않는 실수겠지만 주의해야할 사항이다.

 

 

 

make_shared 간편하게 사용하자

C++11의 auto와 make_shared를 통해 shared_ptr을 간편하게 쓸 수 있다.

 

shared_ptr<TEST> test_ptr(new TEST(10));  와 같이 사용하던것을

 

auto test_ptr = make_shared<TEST>(10);   과 같이 사용할 수 있겠다.

 


 

unique_ptr

shared_ptr처럼 동적으로 할당된 객체를 관리하지만

객체를 독점적으로 관리한다는 차이가 있다.

 

원칙적으로 복사생성자와 할당연산자가 구현되어있지 않다.

 

즉 복사를 할 수 없고 단지 이동(std::move)만 가능하다.

 

사용법자체는 shared_ptr과 동일하다.

 

 

 

배역객체를 다룰때는 shared_ptr과 달리 삭제자를 지정하지않고

unique_ptr<TEST[ ]>test_ptr(new TEST[3]); 과 같이 사용할 수 있다.

 

삭제자를 지정하려면 템플릿 인자에 형을 선언해야하는점이 shared_ptr과는 다르기도하다.

void DeleterArray(TEST* p_test)

{

cout << "Call DeleterArray: p_test1" << endl;

delete[] p_test;

}

 

unique_ptr<TEST[], decltype(&DeleterArray)> test_ptr(new TEST[3], DeleterArray);

 


 

enable_shared_from_this

http://www.boost.org/doc/libs/1_61_0/libs/smart_ptr/shared_ptr.htm#BestPractices

http://en.cppreference.com/w/cpp/memory/enable_shared_from_this

 

shared_ptr로 안전하게 this를 얻어내기위해서 사용

왜 문제가되는가? 는 다음의 좋은사용과 나쁜사용을 통해 알아볼 수 있다.

 

#include <iostream>

#include <memory>

using namespace std;

 

struct Good : enable_shared_from_this<Good>

{

shared_ptr<Good> getptr()

{

return shared_from_this();

}

};

 

struct Bad

{

shared_ptr<Bad> getptr() 

{

return shared_ptr<Bad>(this);

}

~Bad() { cout << "Bad::~Bad() called\n"; }

};

 

int main()

{

// Good: the two shared_ptr's share the same object

shared_ptr<Good> gp1(new Good);

shared_ptr<Good> gp2 = gp1->getptr();

cout << "gp2.use_count() = " << gp2.use_count() << '\n';

cout << "gp1.use_count() = " << gp1.use_count() << '\n';

 

// Bad, each shared_ptr thinks it's the only owner of the object

shared_ptr<Bad> bp1(new Bad);

shared_ptr<Bad> bp2 =( bp1->getptr());

cout << "bp2.use_count() = " << bp2.use_count() << '\n';

 

return 0;

} // UB: double-delete of Bad

 

 



출처: http://heurinbada.tistory.com/118 [흐린바다]

 

 

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

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

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

 

 

출처: http://hmjo.tistory.com/202

 

RAII

RAII는 C++에서 자주 쓰이는 idiom으로 자원의 안전한 사용을 위해 객체가 쓰이는 스코프를 벗어나면 

자원을 해제해주는 기법이다. C++에서 heap에 할당된 자원은 명시적으로 해제하지 않으면 해제되지 않지만, 

stack에 할당된 자원은 자신의 scope가 끝나면 메모리가 해제되며 destructor가 불린다는 원리를 이용한 것이다.

 

스마트 포인터 정의

자원관리 기법에 사용되며 메모리 자원 관리와 경계 검사 등 

기존 포인터 변수에서 기능이 추가 된 추상 데이터 타입.

 

스마트 포인터의 특징

자원을 획득한후, 자원 관리 객체에게 넘긴다

자원관리 객체는 자신의 소멸자를 사용하여 자원이 확실하게 해제 되도록 한다

버그 보완(안전), 자동청소, 자동 초기화를 해준다

스마트 포인터는 C++에서 안전하고 효율적인 코드를 작성하는데 유용하다

delete를 이용해 직접 메모리를 해제하지 않고, 스마트 포인터의 소멸자에 존재하는 delete 키워드를 이용해서 메모리를 삭제한다

원본 삭제 및 참조 카운팅 등을 이용해 댕글링 포인터가 되는 것을 막는다

 

스마트 포인터의 장점

Dangling Pointer로 인해 메모리 누수 현상을 방지할 수 있다.

 

Dangling Pointer란?

두 가지 경우

첫 번째 - delete로 해제된 메모리를 가리키는 포인터.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "stdafx.h"
#include <iostream>


void func(int* ptr);


int _tmain(int argc, _TCHAR* argv[])
{
    int* int_ptr = nullptr;


    int_ptr = new int(10);


    delete int_ptr;


    // dangling
    printf("%d", *int_ptr);


    return 0;
}
Colored by Color Scripter
cs

 

두 번째 - 스택 지역에서 사라진 메모리를 가리키는 포인터.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "stdafx.h"
#include <iostream>


void func(int* ptr);


int _tmain(int argc, _TCHAR* argv[])
{
    int* int_ptr = nullptr;


    func(int_ptr);


    printf("%d", *int_ptr);


    return 0;
}


void func(int* ptr)
{
    int dangling  = 10;
    ptr = &dangling;
}
Colored by Color Scripter
cs

 

사용하는 이유

코드 간략화

자동 해제

자동 초기화

Dangling Pointer 방지

Exception 안전 

- 정상적인 함수 종료에 의한 함수 종료든 Exception이 발생하였든 소멸자는 항상 호출된다.

 

가비지 컬렉션 

- 일부 언어들은 자동 가비지 컬렉션 기능을 제공하지만 C++은 그렇지 못하다.

스마트 포인터는 가비지 컬렉션 용도로 사용할 수 있다.

 

효율성 

- 스마트 포인터는 가용한 메모리를 좀 더 효율적으로 사용할 수 있게 하며 할당, 해제 시간을 단축 시킬 수 있다.

COW( Copry On Write ) : 1개의 객체가 수정되지 않는 동안 여러 COW 포인터가 해당 객체를 가리킬 수 있도록 하되, 해당 객체가 수정되는 경우 COW 포인터가 객체를 복사 한 후 복사 본을 수정하는 방법

객체가 할당되거나 운용되는 환경에 대해 일부 가정을 세울 수 있는 경우 최적화된 할당 계획이 가능하다.(운영 체제나 응용 프로그램이 변경된다 하더라도 클래스의 코드를 최적화된 할당 계획을 만들 수 있다.)

 

스마트 포인터의 단점

스마트 포인터가 NULL 인지 체크 불가

상속 기반의 변환 제한

상수 객체에 대한 포인터 지원 제한

구현하기 까다롭다.

이해하기 쉽지 않아 유지보수도 어렵다.

디버깅이 어렵다.

 

스마트 포인터의 종류

STL 라이브러리에 공식직원하는 스마트 포인터는

auto_ptr, shared_ptr, unique_ptr, weak_ptr이 있다.

그리고 부스터 라이브러리에서 제공하는 scoped_ptr, shared_array 등이 있다.

 

auto_ptr

정의

포인터 변수와 비슷하게 동작하는 객체로써, 가리키고 있는 대상(동적 할당 된 대상)에 대해 auto_ptr 클래스의 소멸자가 자동으로 delete를 호출하는 클래스이다.

 

특징

복사 시 소멸식 복사를 하기 때문에 소유권을 이전한다. 그러므로 컨테이너에 절대 넣으면 안 된다.

내재된 포인터에 대해 오직 하나만의 소유만을 허용한다.

C++11에 이르러 표준에서 제외되었으며 유사한 기능성에, 보다 안정적인 unique_ptr로 대체되었다.어떠한 자원을 가리키는 auto_ptr의 개수가 둘 이상이면 절대로 안되기 때문에, auto_ptr 객체를 복사하면(복사 생성자 혹은 복사 대입 연산자를 통해) 원본 객체가 가리키는 값을 null로 만든다.즉, 복사된(copying) 객체만이 그 자원 의 유일한 소유권(ownership)을 갖는다.

소유권이 옮겨지면 일반 포인터와 다르게 null 포인터가 된다.

 

shared_ptr

정의

참조 카운팅 방식 스마트 포인터( RCSP : Reference-Counting Smart Pointer )이다.

가리키고 있는 객체에 대해 소유권을 가지지 않고 참조 카운트만 유지하는 포인터이다.

 

특징

다른 객체를 가리키면서 포인터에 대한 소유권을 공유할 수 있다(aliasing). 해당 포인터에 대한 소유권을 가지지 않고 참조 카운팅만 유지하는 방식이다.

자원을 참조하는 외부 객체의 개수를 관리하고 있다가, 그 개수가 0이 되면 해당 자원을 자동으로 삭제한다.

STL 컨테이너 사용이 가능하다.

가비지 컬렉션( Garbage Collection )의 동작과 흡사하지만, 가비지 컬렉션과는 달리 참조 상태가 고리를 이루는 경우( 서로 다른 두 객체가 서로를 참조하는 경우 )는 없앨 수 없다.

 

unique_ptr

정의

C++11에 이르러 포함되었으며 auto_ptr을 보다 개선한 형태(배열 삭제)를 지원한다.

 

특징

소유권 독점 방식을 사용하는 스마트 포인터로, auto_ptr과 유사하나 복사 생성자와 대입 연산자가 제공되지 않는다.

복사 생성자와 대입 연산자를 제공하지 않기 때문에 복사를 할 수 없으며, 소유권 이전을 위해서 std::move()를 사용한다.

소유권 해제 시점은 포인터 객체가 가리키는 객체의 소멸, 대입연산, reset 메소드에서 명시적 호출을 통해 값을 변경할 때 이다.

소유권을 독점하는 방식이기 때문에 auto_ptr은 객체에 대한 포인터가 유일하도록 관리해주었어야 하지만, unique_ptr은 스스로 포인터의 유일성을 보장한다.

 

weak_ptr

정의

shared_ptr의 순환 참조로 발생하는 문제를 해결하기 위해 사용하는 특수한 포인터이다.

 

특징

weak_ptr 은 shared_ptr을 관리하기 위한 reference count에 포함되지 않고 shared_ptr의 객체를 참조만 한다. ( shared_ptr 의 weak reference count로 관리)

약한 참조 성질을 지닌다.( 객체가 살아 있도록 유지 시키지 않고, 단순히 객체가 살아 있는 동안만 참조 가능 )

expired 함수를 통해 자신이 참조하고 있는 shared_ptr의 상태를 확인 할 수 있다.

weak_ptr 객체는 포인터에 대해 직접 접근이 불가능 하며, 액세스를 원하면 lock 메소드를 통해 shared_ptr로 변환한 뒤, shared_ptr 의 get 메소드를 사용한다

 



출처: http://hmjo.tistory.com/202 [오이데 오이데 ~~]

 

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

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

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

 

 

출처: https://duragon.gitbooks.io/c-11/content/chapter8.html

 

 

6. 스마트 포인터

unique_ptr

C++11이 등장하기 전에도 이미 std::auto_prt 이라는 스마트 포인트를 사용했었다. 그러나 auto_ptr이 포인터에는 소유권의 문제가 있다. 내부 구현에서 auto_ptr의 복사 생성자와 할당 연산자 구현이 멤버 데이터에 대한 깊은 복사(전체 내용의 복사) 대신 얕은 복사(포인터만 복사)를 하도록 되어 있기 때문이다. 그래서 함수 안으로 온전한 auto_prt객체를 전달하기 힘들다는 문제가 있다.

함수 인자로 auto_ptr을 전달하면 복사 생성자가 호출되고 그 결과 얕은 복사가 발생하기 때문이다. 얕은 복사를 하는 특성 덕분에 특정 순간 객체의 소유권이 유일하게 하나의 auto_ptr 객체에만 존재한다는 장점도 있다. 말 그대로 복사를 허용하지 않는 것이다. 하지만 이러한 장점을 전부 덮어버릴 만한 단점이 있었으니, auto_ptr 객체는 복사가 필요한 곳에서는 사용될 수 없다는 점이다.

새로운 C++11 표준에서는 복사 시맨틱에 추가로 새로운 개념인 이동 시맨틱이 등장했다. 이동 시맨틱은 복사 시맨틱처럼 두 객체 사이에 복사를 수행하는 대신, 객체의 데이터 필드 하나하나를 이동시키는 역할을 수행한다. 두 객체간의 데이터 이동이 발생한 후에는 해당 데이터에 대한 소유권은 데이터를 받는 쪽이 갖는다. 이동 시맨틱이 필요한 이유는 무엇일까? STL의 벡터나 리트스와 같은 컨테이너의 경우, 일동의 동적 배열이기 때문에 그 크기가 상황에 따라 두배씩 늘어난다. 이때 메모리 내부에서는 대량의 복사가 발생한다. 그리고서 원본은 파괴하고 사본만 사용을 한다.

단지 배열의 크기를 늘리기 위해서 불필요한 복사 동작을 하는점, 그리고 이로 인해 쓸데없는 객체를 생성하거나 사용하지 않는 원본 객체를 파괴한다는 점등 일련의 불필요한 동작은 C++성능 저하의 주범이라는 눈총을 받았다. 이런 경우 복사보다는 이동이 낫겠다고 판단하여 이동시맨틱을 도입했고, 이를 위해 이동 생성자라는 개념을 도입했다.

C++에서는 std::unique_ptr이라는 이름의 새로운 단일 포인터 타입을 도입하여, std::auto_ptr과 하위호환을 이룬다. std::unique_ptr 내부에서는 복사 생성자와 할당 연산자가 아예 구현되어 있지 않다. unique_ptr객체는 복사가 원천 봉쇄되어 있고 단지 이동만 가능하다. 반드시 std::move()라는 함수를 이용해야만 이동할 수 있다.

#include <memory> // for unique_ptr int main(int argc, char** argv) { std::unique_ptr<int> p1(new int(5)); // std::unique_ptr<int> p2 = p1; // compile Error (복사를 허용하지 않음) std::unique_ptr<int> p3 = std::move(p1); // move p3, p1은 존재하지 않음. p3.reset(); // 메모리 영역 초기화 p1.reset(); // 이미 없으므로 효과없음. } #include <memory> using namespace std; int main(int argc, char** argv) { unique_ptr<int> p1(new int(5)); // create p1 unique_ptr<int> p3 = move(p1); // move to p3 cout<<p1.get()<<endl; cout<<p3.get()<<endl; // 주소 반환 cout<<*p3<< endl; // (5)라는 값을 얻음 auto a = *p3; cout<<a<< endl; // (5) auto& a2 = p3; cout<<*a2<< endl; // (5) // auto b = *p1; // 런타임 에러(p3로 이전됨) // cout<<b<< endl; // 런타임 에러 p3.reset(); // 메모리 삭제 p1.reset(); // 아무것도 하지 않음 return 0; } #include <memory> // unique_ptr #include <iostream> // cout, endl #include <string> using namespace std; class Person { public: Person() {}; Person(int age, std::string name) : age(age), name(name) {} ~Person() {}; public: int GetAge() { return age; } string GetName() { return name; } private: int age; string name; }; int main(int argc, char** argv) { unique_ptr<Person> p(new Person(1, "Baby")); cout<<"Name: "<<p->GetName()<<endl; cout<<"Age: "<<p->GetAge()<<endl; getchar(); return 0; }

shared_prt

shared_ptr은 이름이 의미하듯 포인터가 가리키는 객체의 소유권을 이곳 저곳에서 공유할 수 있도록 디자인된 포인터이다. 바로 이 점이 unique_ptr과 shared_ptr을 구별하는 가장 큰 성질이라고 할 수 있다. 복사를 허용하지 않았던 uinique_ptr과는 달리 shared_ptr에서는 특정 보인터 객체를 여러 개의 shared_ptr 객체가 가리키도록 할 수 있다.

이런 shared_ptr을 컴파일어에서 구현하려면 레퍼런스 카운팅 이라는 방법을 사용합니다. 레퍼런스 카운팅은 여기저기로 복사되는 shared_ptr객체를 추적하려고 컴파일러가 몇 번이나 복사되는지 횟수를 기억하는 방식이다. shared_ptr 객체가 새롭게 복사될 때마다 카운터는 하나씩 증가되고, shared_ptr 객체가 삭제될 때는 그만큼 카운터의 횟수를 줄인다 이러한 메커니즘을 이용하여 객체의 저장 메모리 공간은 한 곳만 사용하고, 몇번 복사됐는지 횟수만 기억함으로써 메모리 공간도 절약할 수 있고, 처리속도도 향상시킬 수 있다. 해당 메모리 공간이 해제 되는 시점은 Shared_ptr 객체의 레퍼런스 카운트가 0이 되는 때 이다.

#include <memory> // shared_ptr int main(int argc, char** argv) { std::shared_ptr<int> p1(new int(5)); // create, refcount = 1 std::shared_ptr<int> p2 = p1; // refcount = 2 p1.reset(); // refcount = 1 p2.reset(); // refcount = 0, free return 0; }

  • namespace와 필요헤더
    • namespace : std
    • header : < memory >
  • 선언

class Car {...}; // Resource Acquisition Is Initializing : RAII std::shared_ptr<Car> Avante( new Car() ); // 즉, std::shared_ptr<_Ty> Object( new _Ty(construct) );의 형식을 띈다.

  • Reference Count의 증가와 감소
    • 증가 : shared_ptr 객체의 복사나 대입이 발생하여 참조 shared_ptr 객체 수 증가.
    • 감소 : shared_ptr이 가리키고 있는 객체를 참조하는 shared_ptr 객체 수의 감소.
  • 소멸시 주의 사항
  • 기본적으로, shared_ptr은 소멸시 참조 카운트가 0 이 되면, 참조하는 객체에 대해 delete 연산자를 사용한다. delete만 사용한다는 소리다. 즉, delete [] 따윈 사용해 주지 않는단 말이다.

따라서, 아래와 같이 하면 new-delete, new [] - delete []를 지키지 않았을 때의 문제가 그대로 나타나는 것이다.

std::shared_ptr<int> spi( new int[1024] );

즉, 아래와 같이 하라는 것이다.

std::vector< std::shared_ptr<int> > spVec; spVec.push_back( std::shared_ptr<int>( new int(3) ) );

또한, 배열 삭제를 지원하는 deleter를 지정하여 해결할 수도 있다.

shared_ptr의 생성자 함수는 크게 다음 세 가지 형태로 정의되어 있다.

template<class _Ux> explicit shared_ptr(_Ux *_Px) { // construct shared_ptr object that owns _Px _Resetp(_Px); } template<class _Ux, class _Dx> shared_ptr(_Ux *_Px, _Dx _Dt) { // construct with _Px, deleter _Resetp(_Px, _Dt); } template<class _Ux, class _Dx, class _Alloc> shared_ptr(_Ux *_Px, _Dx _Dt, _Alloc _Ax) { // construct with _Px, deleter, allocator _Resetp(_Px, _Dt, _Ax); }

두 번째 생성자의 정의부터 보이는 class _Dx를 우리가 정의한 클래스로 지정시, 이는 shared_ptr의 참조 카운트가 0 이 될 때의 deleter 클래스가 된다.

// deleter 클래스 정의 template<typename T> struct ArrayDeleter { void operator () (T* p) { delete [] p; } }; // shared_ptr 생성시 두 번째 인자로 deleter class를 넘기면... // 아무런 문제없이 객체 배열도 제대로 delete [] 처리가 된다. std::shared_ptr<int> spi( new int[1024], ArrayDeleter<int>() );

  • 참조 객체 형변환 
    shared_ptr 비멤버 함수를 통해 shared_ptr이 참조하고 있는 객체의 형변환을 수행할 수 있다.

template<class _Ty1, class _Ty2> shared_ptr<_Ty1> static_pointer_cast(const shared_ptr<_Ty2>& _Other) { // return shared_ptr object holding static_cast<_Ty1 *>(_Other.get()) return (shared_ptr<_Ty1>(_Other, _Static_tag())); } template<class _Ty1, class _Ty2> shared_ptr<_Ty1> const_pointer_cast(const shared_ptr<_Ty2>& _Other) { // return shared_ptr object holding const_cast<_Ty1 *>(_Other.get()) return (shared_ptr<_Ty1>(_Other, _Const_tag())); } template<class _Ty1, class _Ty2> shared_ptr<_Ty1> dynamic_pointer_cast(const shared_ptr<_Ty2>& _Other) { // return shared_ptr object holding dynamic_cast<_Ty1 *>(_Other.get()) return (shared_ptr<_Ty1>(_Other, _Dynamic_tag())); } class Car {...}; class Truck : public Car {...}; // Truck 타입의 객체를 Car 타입의 객체를 참조하는 shared_ptr에 초기화 shared_ptr<Car> pCar( new Truck() ); // shared_ptr<Car>가 참조하고 있던 객체를 Truck 타입으로 static_cast하여 대입. // 대입 하였기에 참조 카운트는 '2' shared_ptr<Truck> pTruck = static_pointer_cast<Truck>(pCar); // 위처럼 대입하지 않고 스스로 형변환만 하여도 상관없음. // 참조 카운트는 당연히 변화가 없다. static_pointer_cast<Car>(pCar);

  • 참조 객체 접근

명시적 방법

* shared_ptr::get() : 참조하고 있는 객체의 주소를 반환한다.

암시적 방법

* shared_ptr::operator* : 참조하고 있는 객체 자체를 반환한다. : 즉, *(get())의 의미 * shared_ptr::operator-> : get()->의 의미와 같다. shared_ptr<Car> spCar( new Truck() ); // spCar가 참조하는 객체의 주소를 반환 Car* pCar = spCar.get(); // spCar가 참조하는 객체의 메써드에 접근 #1 spCar.get()->MemberFunc(); // spCar가 참조하는 객체의 메써드에 접근 #2 *(spCar).MemberFunc(); // spCar가 참조하는 객체의 메써드에 접근 #3 spCar->MemberFunc();

  • 순환참조

대부분 그룹객체 - 소속 객체간 상호참조에서 발생하며 소속객체가 그룹객체를 weak_ptr로 들고 있으면 해결된다. (아래 weak_ptr 참고)

  • 멀티쓰레드 안정성

안전하지 않다. thread_safety는 동시에 읽을 경우에만 안전하고, 쓰기를 할 경우에는 안전하지 않다.

weak_ptr

앞에서 shared_ptr은 포인터가 가리키는 실제 메모리가 몇 번이나 복사되어 사용되는지 내부적으로 추적하기 위해 레퍼런스 카운팅 방식을 이용한다. 하지만, 이 레퍼런스 카운팅 방식의 잠재적인 위험 가운데 하나로 서로를 참조하는 순환참조 문제가 있다. A는 B를 가리키고, B는 다시 A를 가리키는 상황이 바로 순환참조 상황이다. 이런 상황에서는 순환참조에 참여하는 모든 인스턴스가 삭제될 수 없으며, 이는 곧장 메모리 누수로 이어진다. 이런 문제를 해결하는 포인터 타입이 weak_ptr 이다.

shared_ptr에서는 메모리를 참조하는 shared_ptr이 자신을 제외하고 하나라도 남아 있으면, 아무리 삭제 명령을 내려도 해당 메모리가 삭제되지 않는다. 그러나 해당 메모리를 가리키는 포인터 타입이 shared_ptr이 아닌, weak_ptr 이라면 해당 메모리는 삭제될수 있다. weak_ptr이 가리키는 메모리 공간은 shared_ptr이 메모리를 관리하려고 사용하는 레퍼런스 카운트에 포함되지 않기 때문디다. 즉, 순환참조가 일어날 수 없다.

사실 weak_ptr이 shared_ptr을 참조할 때 shared_ptr의 weak reference count는 증가시킨다. 객체의 생명 주기에 관여하는 strong reference count를 올리지 않는 것 뿐이다. (shared_ptr, weak_ptr 객체를 디버거로 살펴보면 strong/weak refCount가 따로 표시된다)

weak_ptr은 특정 메모리 번지를 참조하는 shared_ptr이 아직 존재하는지 여부를 확인해볼 때 사용 할 수 있다. weak_ptr이 shared_ptr을 가리키고 있을 때 shared_ptr이 해제되면 weak_ptr도 해제된다.

#include <memory> // shared_ptr #include <iostream> int main(int argc, char** argv) { std::shared_ptr<int> sp1(new int(5)); // create, ref = 1 std::weak_ptr<int> wp1 = sp1; // ref = 1 { // sp1은 wp1으로 sp2를 복사 std::shared_ptr<int> sp2 = wp1.lock(); // ref = 2 if(sp2) { std::cout<<"sp2 has copy of sp1"<<std::endl; // ❹ } } // sp2는 이곳에서 자동으로 파괴됨, ref = 1 sp1.reset(); // sp1은 이곳에서 파괴됨, ref = 0, free std::shared_ptr<int> sp3 = wp1.lock(); // null if(sp3) { std::cout<<"it's impossible to be here"<<std::endl; // can't display } return 0; }

weak_ptr은 shared_ptr의 참조자라고 표현하는 것이 맞을 듯 하다. 같은 weak_ptr 또는 shared_ptr로부터만 복사 생성/대입 연산이 가능하며, shared_ptr로만 convert가 가능하다.

따라서, weak_ptr<_Ty>는 _Ty 포인터에 대해 직접 access가 불가능하며, (shared_ptr의 get() 메쏘드 같은 녀석이 아예 없다) _Ty 포인터에 엑세스를 원하면 lock 메써드를 통해 shared_ptr로 convert 한 뒤, shared_ptr의 get 메쏘드를 사용해야 한다.

그리고 expired 함수를 통해 자신이 참조하고 있는 shared_ptr의 상태(즉, weak_ptr의 상태)를 체크할 수 있다.

shared_ptr<_Ty> lock() const { // convert to shared_ptr return (shared_ptr<_Elem>(*this, false)); } bool expired() const { // return true if resource no longer exists return (this->_Expired()); }

순환참조 예제

  • 순환참조의 대부분은 그룹객체 - 소속객체간 상호참조에서 발생한다.

#include <memory> // for shared_ptr #include <vector> using namespace std; class User; typedef shared_ptr<User> UserPtr; class Party { public: Party() {} ~Party() { m_MemberList.clear(); } public: void AddMember(const UserPtr& member) { m_MemberList.push_back(member); } void RemoveMember() { // 제거 코드 } private: typedef vector<UserPtr> MemberList; MemberList m_MemberList; }; typedef shared_ptr<Party> PartyPtr; typedef weak_ptr<Party> PartyWeakPtr; class User { public: void SetParty(const PartyPtr& party) { m_Party = party; } void LeaveParty() { if (m_Party) { // shared_ptr로 convert 한 뒤, 파티에서 제거 // 만약, Party 클래스의 RemoveMember가 이 User에 대해 먼저 수행되었으면, // m_Party는 expired 상태 PartyPtr partyPtr = m_Party.lock(); if (partyPtr) { partyPtr->RemoveMember(); } } } private: // PartyPtr m_Party; PartyWeakPtr m_Party; // weak_ptr을 사용함으로써, 상호 참조 회피 }; int _tmain(int argc, _TCHAR* argv[]) { // strong refCount = 1; PartyPtr party(new Party); for (int i = 0; i < 5; i++) { // 이 UserPtr user는 이 스코프 안에서 소멸되지만, // 아래 party->AddMember로 인해 이 스코프가 종료되어도 user의 refCount = 1 UserPtr user(new User); party->AddMember(user); // weak_ptr로 참조하기에 party의 strong refCount = 1 user->SetParty(party); } // for 루프 이후 strong refCount = 1, weak refCount = 5 // 여기에서 party.reset을 수행하면, strong refCount = 0 // 즉, 파티가 소멸되고 그 과정에서 m_MemberList가 clear -> user들의 strong RefCount = 0 -> user 소멸 // party와 5개의 user 모두 정상적으로 소멸 party.reset(); return 0; }

정리하자면, 
weak_ptr은 다음과 같은 경우에 사용하면 유용하다.

  • 어떠한 객체를 참조하되, 객체의 수명에 영향을 주고 싶지 않은 경우
  • 매번 특정 객체의 ID로 컬렉션에서 검색하고 싶지 않을 경우
  • 그러면서 dangling pointer의 잠재 위험성을 없애고 싶을 때

 

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

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

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

 

 

출처: http://skmagic.tistory.com/entry/%EC%8A%A4%EB%A7%88%ED%8A%B8%ED%8F%AC%EC%9D%B8%ED%84%B0

 

스마트포인터!?

 

스마트포인터!?
간략하게 소개하면 
메모리 누수를 막기위한 기술이다

즉 포인터를 관리하는 매니저클래스 비스 무리한것을 두고 소멸자에서 동적할당 메모리를 해제해주는 식의 기법

 

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

 

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

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

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

 

* 첨부된 예제에서 사용된 스마트 포인터는 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/140059062664  

 


 
하악!!!!
스마트 포인터라는것을 배워서 한번 정리해봄~
정리를 하면 포인터를 관리하는 매니저클래스 비스 무리한것을 두고 소멸자에서 동적할당 메모리를 해제해주는 식의 기법 정도?!

 

 

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

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

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

 

 

출처: http://thrillfighter.tistory.com/215

 

 

이번 포스팅은 스마트 포인터(auto_ptr)에 대해서 알아보겠습니다.

 

C++11에선 (스마트 포인터)shared_ptr이 표준에 추가되었다. 이는 참조 카운팅을 추가하여 스마트 포인터간의 매끄러운 대입을 가능하게 하였다. 스마트포인터의 종류에 대한 설명은 다음 포스팅으로 미루고 여기에선 스마트포인터의 원리에 관해서 원론적인 이야기를 할까 한다.

 

내용에 들어가기 앞서 스마트 포인터에 관한 이해를 위해서는 다음과 같은 주제에 대한 이해가 필히 요구된다.

템플릿, 연산자 오버로딩, 정적할당, 동적할당, 스택, 힙, 생성자,파괴자...

 

 

2014/06/16 - [프로그래밍/c++] - C++ 템플릿, 함수템플릿에 대한 이해

 

2014/06/17 - [프로그래밍/c++] - 클래스 템플릿에 대한 이해 C++

 

2014/06/18 - [프로그래밍/c++] - 비타입 인수,디폴트 템플릿 인수,특수화 (클래스,함수 템플릿) c++

 

 

2014/03/19 - [프로그래밍/c++] - 연산자 오버로딩 C++

 

 

2013/05/09 - [프로그래밍/c++] - 생성자와 소멸자 호출 C++

 

2014/04/21 - [프로그래밍/c++] - c++ 생성자 소멸자/(virtual)가상 소멸자를 쓰는 이유

 

c++을 모른다면 생성자와 소멸자(파괴자)에 대해 생소하게 생각할 수 있다. 이 글은 c++에 관한 글이므로  c언어를 공부중이라면 그냥 이런게 있구나 하고 이해 정도는 해주자.

 

c언어에서 동적할당과 메모리 해제를 malloc/free 로 한다면 c++은 new/delete 가 있다. c++에서 모든 타입의 동적할당은 다음과 같은 표현으로 가능하다.

Type* A = new Type();

(c++에서는 구조체, 클래스도 하나의 타입으로 인정하고 위와 같이 동적할당을 할 수 있다.)

 

 

 

 

int형 포인터 변수 a는 메모리의 스택영역에 생성되었고 4의값을 가지는 int  타입의 데이타가 힙 영역에 할당된다. a포인터는 이 값을 가리킨다.

point클래스형 포인터 A도 스택영역에 할당되었고, 힙영역에는 실질적인 point객체가 생성되었다.

이 값들은 main 함수 영역내에서 생성된 것이며 프로그램이 종료될 때 메모리에서 해제가 되어야 한다. 위 예처럼 delete 구문을 주석처리를 해놓으면 main함수 종료 시 스택영역에 할당된 a,A 포인터만 메모리 해제가 되고 힙영역 데이타는 해제되지 않는다. 이렇게 힙영역에 할당된 메모리를 해제하지 않으면, 시스템에서는 아직 사용중으로 인식하여 이 부분의 메모리를 사용을 하지 않는다. 메모리누수(memory leak)의 원인인 것이다. 포인터와 동적할당 객체는 단지 주소값으로만 연결되어 있는 위태로운 상황이다. 포인터가 없어지면 실질적인 값만 남게 된다. 더 이상 사용되어지지 않는 실질적인 값을 가비지(garbage)라 불린다. 이 값은 하나의 사용 가능한 정보 데이타지만, 프로그램 입장에선 더 이상 의미없는 정보이고, 메모리 누수(memory leak)을 일으킨다. 

 

자바의 유명한 가비지 컬렉션(garbage collection)은 이렇게 더 이상 사용되어지지 않는 메모리를 주기적으로 정리하는 기능입니다. 우리의 c++은 그딴 거 없습니다. 무조건 프로그래머의 몫입니다. 그런데,

 

힙영역에 동적할당된 메모리를 해제하기 위해선 delete만 쓰면 간단히 해결된다.

 

이렇게 간단하게 해결할 수 있는 문제에 굳이 스마트 포인터라는 것까지 등장하게 된 이유는 무엇일까?

첫째, 이런 메모리 누수는 프로그램에 치명적이지 않다. 물론 에러발생도 하지 않는다. 이런 이유로 이런 메모리 해제 작업을 소홀히 할 수 있다.

둘째, 예기치 않은 에러 등으로 프로그램이 종료될 경우 힙영역에 아직 가동중인 데이타의 해제가 제대로 일어나지 않을 수 있다. 또한 장기간 실행되어야 하는 서버 프로그램의 경우 작은 메모리 누수도 메모리 관리에 치명적으로 작용할 수 있다.

이런 문제점들을 개선하기 위해 스마트 포인터를 도입한 듯 하다.(이건 제 주관적인 견해입니다.)

 

스마트 포인터의 원리

다음은 이 원리를 이해하기 위한 내용이다.

 

1.함수내의 지역변수는 그 함수의 반환 시 같이 스택 내에서 해제된다. 

2.객체의 경우 메모리에서 해제가 일어나면 파괴자(소멸자)를 호출하는데 파괴자(소멸자)의 기본 역할은 생성자가 생성한 메모리를 해제하는 역할을 한다. 생성자의 역할은 객체의 생성 시 멤버변수의 초기화를 담당한다.

 

다음은 스마트 포인터를 흉내낸 템플릿 클래스다.

 

 

 

 

모든 타입에 가능한 사용을 위해 템플릿으로 만들었고, 단순히 할당과 해제만 가능하도록 만들었다. int형 값 3이 동적 할당되어 smpt 클래스 객체인 test의 멤버 변수가 이 값을 가리키도록 초기화 되었다.(smpt 생성자가 동적할당된 객체의 포인터를 인수로 받아들인다.) 프로그램이 종료되고 main 함수 반환시 test 객체는 스택에서 해제된다. 객체가 메모리에서 해제될 때 파괴자(소멸자)는 자동으로 호출되므로 파괴자 내부에 delete p;가 작동하여 자동으로 동적 할당된 객체가 해제된다. 잘보면 test는 포인터가 아니다. 요점은 smpt 클래스의 객체 test변수는 스택에 할당된 값이므로 함수 리턴 시 자동으로 해제되고, 포인터가 아닌 객체 자체이므로 해제될 때 파괴자를 호출하여 delete p;를 언제나 호출한다는 것이다.

 

auto_ptr 의 정의(memory 헤더파일)

template <typename T> auto_ptr class {...}

 

auto_ptr의 사용

auto_ptr<T> test(new T(.,.))

 

그냥 사용법만 외우고 사용만 해도 상관은 없겠다. 그래도 이왕 살펴 봤으니 좀 더 깊게 살펴보려고 한다.

 

말은 스마트 포인터지만, 실제로 포인터는 아니다. 구체적으로 말하면, 포인터를 래핑한 포인터의 래퍼클래스라고 말 할 수 있다.

실제 포인터가 아닌 스택에 저장되는 객체 변수이므로 이를 이용하여 파괴자 호출을 통한 delete의 안정적인 호출을 할 수 있지만, 객체변수라서 생기는 문제점을 보완해야 한다.

 

다음 코드를 분석하여 문제점을 보완해 나가겠다.

 

 

 

 

x,y좌표값을 멤버변수로 갖는 point 클래스를 선언하고 포인터 클래스로 객체를 생성하여 좌표값을 출력하려 하였다. 다음과 같은 컴파일 에러가 발생한다.

 

 

 

 

스마트 포인터의 목적은 달성하였지만, 주석처리된 코드처럼 사용하려면 연산자에 대한 오버로딩이 필요하다. 실제로 a는 point클래스의 포인터가 아닌 래퍼 클래스 객체변수이기 때문이다.

 

2014/03/19 - [프로그래밍/c++] - 연산자 오버로딩 C++

 

연산자 오버로딩에 대한 문법은 위 글을 참조하시길 바랍니다.

 

 

 

 

 

 

->, * 연산자 오버로딩

T* operator->() const {return T}

a->print();  // a.operator->()->print(); (사실, 이 부분은 잘 이해가 가지는 않는다. 그냥, 이렇게 해석되는구나 하고 넘어감)

위와같이 해석되어 a.operator->() 이 T가 되어야 함.

 

* 연산은 그냥 이해하면 된다.

 

이 외에도 auto_ptr은 더 많은 연산자 오버로딩을 가질 것이다.  이 부분은 추후에 좀더 살펴보도록 하겠다.

 

 

 

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

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

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

 

 

 

출처: http://egloos.zum.com/sweeper/v/2826435

[TR1] shared_ptr

http://sweeper.egloos.com/2826435

 

1. auto_ptr

 

TR1이 발표되기 전까지 std::auto_ptr이 C++ Standara library의 유일한 스마트 포인터였다.

 

스마트 포인터의 기본적인 특성인 자신이 소멸될 때 가리키고 있는 대상에 대해 자동으로 delete 해줘 메모리 누수 걱정은 없게 작성이 되어 있다.

 

하지만, auto_ptr은 유일 소유권 개념이 있어서, 객체가 복사되는 순간(복사생성 또는 대입연산) 원래의 auto_ptr은 바로 NULL 처리가 되어 버린다.

 

  1. class AAA;
  2.  
  3. // RAII 방식으로... AAA 객체 생성
  4. std::auto_ptr<AAA> AAAObject(new AAA());
  5.  
  6. // 복사가 되는 순간, AAAObject는 NULL이 되고, 이제 BBBObject 만이 객체를 가리킨다.
  7. std::auto_ptr<AAA> BBBObject(AAAObject);
  8.  
  9. // 역시 대입이 되는 순간, BBB는 NULL, 이제 AAA가 객체를 가리킴.
  10. AAAObject = BBBObject;

 

이렇듯 괴상망측한 복사 동작으로 인해 STL의 컨테이너에서도 전혀 환영받지 못하고, (STL 컨테이너들은 정상적인 복사 능력을 가진 원소를 요구한다) 일반적인 프로그래머들 사이에서도 몹쓸 녀석이 되어 버렸다.

 

나 같은 경우도 의미 파악을 위해 MSDN 보고 클래스 한 두번 만들어본 게 전부이지, 실무에 써먹은 적은 한번도 없다.

앞으로도 영원히 쓸 일이 없지 싶으다 -_-;

 

 

2. boost::shared_ptr의 등장

 

저렇듯 C++ Standard Library에 유일하게 하나 있는 스마트 포인터가 병X이다 보니 부스트 형님들이 가만 있지 않았고,

곧 바로 참조 카운팅 방식을 사용하는 스마트 포인터, boost::shared_ptr을 내놓게 된다.

 

즉, shared_ptr은 특정 자원을 가리키는 참조 카운트를 유지하고 있다가 이것이 0 이 되면 해당 자원을 자동으로 삭제해 주는 스마트 포인터인 것이다.

 

참조 카운트는 이를 가리키는 외부 객체의 수가 증가할 때 같이 올라간다.

즉, shared_ptr의 복사나 대입이 발생하면 레퍼런스 카운트가 증가하고, 그 복사/대입되었던 녀석들이 소멸되게 되면 레퍼런스 카운트가 감소하는 것이다.

 

우선, 이 문서는 boost::shared_ptr의 소개를 위한 문서가 아니므로, 이처럼 개념적인 부분만 정리하고 나머지는 링크로 대체한다.

 

boost official homepage : www.boost.org (참고로 2011/07/11 부스트 라이브러리는 1.47.0으로 업데이트 되었다)

boost smart pointer : http://www.boost.org/doc/libs/1_47_0/libs/smart_ptr/smart_ptr.htm

boost::share_ptr : http://www.boost.org/doc/libs/1_47_0/libs/smart_ptr/shared_ptr.htm

 

 

3. TR1::shared_ptr

 

C++ Standard Library는 많은 것을 포함하고 있는, 엄청나게 방대한 라이브러리이지만 세월이 흐름에 따라 새로운 요구 사항들이 계속해서 발생하게 되었다.

특히 이러한 패러다임의 충실하게 반영되고 있는 boost 진영의 소리없는 압박도 C++ Standard Library의 변화 유발을 촉구시켰다.

 

2005년 5월, 드디어 Technical Report 1, 즉 TR1이 발표되었고, 여기엔 꽤나 많은 부분들이 추가되었다.

 

이들의 대부분은 boost 진영에서 개발되어 전세계 수많은 프로그래머들에 의해 이미 검증된 것들이고, 그 중 하나가 TR1::shared_ptr (from boost::shared_ptr) 이다.

 

TR1::shared_ptr (이하 shared_ptr이라고만 쓰겠음)은 태생이 boost::shared_ptr이라 거의 모든 구현 내용이 boost::shared_ptr과 똑같다.

 

우선 MSDN 링크는 다음과 같다 : http://msdn.microsoft.com/ko-kr/library/bb982026

 

아래 사용법들과 예제를 통해 shared_ptr의 특징을 간단히 정리해 보자.

 

1) namespace와 필요 헤더 파일

  • namespace : std
  • header : <memory>

 

2) 선언

 

shared_ptr의 선언은 아래와 같이 RAII idiom을 따른다.

 

  1. class Car {...};
  2.  
  3. // Resource Acquisition Is Initializing : RAII
  4. std::shared_ptr<Car> Avante( new Car() );

 

즉, std::shared_ptr<_Ty> Object( new _Ty(construct) );의 형식을 띈다.

 

 

3) Reference count의 증가와 감소

  • 증가 : shared_ptr 객체의 복사나 대입이 발생하여 참조 shared_ptr 객체 수 증가.
  • 감소 : shared_ptr이 가리키고 있는 객체를 참조하는 shared_ptr 객체 수의 감소.

 

<참조 카운트 예제>

 

  1. class Car {...};
  2.  
  3. // 값 전달, 복사에 의한 임시객체 생성, 함수 종료시 생성된 임시객체 소멸
  4. // 하지만 아래 매개변수를 const std::shared_ptr<Car>&로 받는다면,
  5. // 임시 객체가 생기지 않아서 참조 카운트가 올라가지 않는다.
  6. void function( std::shared_ptr<Car> _car )
  7. {
  8.         ...
  9. }
  10.  
  11. int main()
  12. {
  13.         // 최초 생성시 초기 참조 카운트는 당연히 '1'
  14.         std::shared_ptr<Car> Car1( new Car() );
  15.         // 복사 -> 참조 카운트 '2'
  16.         std::shared_ptr<Car> Car2(Car1);
  17.         // 대입 -> 참조 카운트 '3'
  18.         std::shared_ptr<Car> Car3 = Car1;
  19.  
  20.         // function( std::shared_ptr<Car> _car ), 값에 의한 전달, 복사에 의한 임시객체 생성
  21.         // 이로 인한 참조 카운트 증가 -> '4'
  22.         function( Car3 );
  23.         // 함수 호출 후엔 임시객체 소멸되므로 참조 카운트 감소 -> '3'
  24.  
  25.         // reset 함수는 shared_ptr이 참조하는 객체를 새로운 녀석으로 바꿀 수 있는 함수이다.
  26.         // 내부적으로 shared_ptr::swap 함수가 사용됨
  27.         // http://msdn.microsoft.com/ko-kr/library/bb982757.aspx
  28.         // 인자를 주지 않으면 참조 포기가 되는 것이다. 따라서 참조 카운트 감소 -> '2'
  29.         Car3.reset();
  30.         ...
  31.         return 0;
  32.         // 함수 반환시 남아있던 shared_ptr 모두 소멸 -> 참조 카운트 '0'
  33.         // 이제 shared_ptr이 참조하고 있던 Car * 에 대해 delete가 호출됨.
  34. }

 

 

4) shared_ptr의 참조 해제

 

shared_ptr의 refCount == 1 인 상태에서 원래 참조하고 있던 객체가 아닌 다른 객체를 참조하게 되면, 원래 참조하고 있던 객체는 delete 처리가 된다.

 

이해엔 예제가 따봉~

 

  1. class Car {...};
  2.  
  3. // 최초 생성시 초기 참조 카운트는 당연히 '1'
  4. std::shared_ptr<Car> Car1( new Car() );
  5. // 최초 생성시 초기 참조 카운트는 당연히 '1'
  6. std::shared_ptr<Car> Car2( new Car() );
  7.  
  8. // Car1 shared_ptr은 이제 Car2의 객체를 참조한다.
  9. // Car1이 참조하던 Car* 는 더 이상 참조자가 존재하지 않아, delete가 호출된다.
  10. // 대신 Car2가 참조하던 객체를 이제 Car1 shared_ptr도 참조하므로 참조 카운트는 '2'
  11. Car1 = Car2;

 

 

5) shared_ptr 소멸시 주의사항

 

기본적으로, shared_ptr은 소멸시 참조 카운트가 0 이 되면, 참조하는 객체에 대해 delete 연산자를 사용한다.

응?! delete만 사용한다는 소리다. 즉, delete [] 따윈 사용해 주지 않는단 말이다.

 

따라서, 아래와 같이 하면 new-delete, new [] - delete []를 지키지 않았을 때의 문제가 그대로 나타나는 것이다.

 

std::shared_ptr<int> spi( new int[1024] );

 

이는 vector등으로 표현할 수 있기에 굳이 TR1에 포함되지 않았을 것이라고 추측해 보지만, 뭐 불편하긴 하다.

즉, 아래와 같이 하라는 것이다.

 

std::vector< std::shared_ptr<int> > spVec;

spVec.push_back( std::shared_ptr<int>( new int(3) ) );

 

(부스트의 scoped_array나 shared_array가 그리운가? 쩝;;;)

 

위 방법 외에도 배열 삭제를 지원하는 deleter를 지정하여 해결할 수도 있다.

 

이는 다음 "deleter 지정"에서 설명하겠다.

 

 

6) deleter 지정

 

shared_ptr의 생성자 함수는 크게 다음 세 가지 형태로 정의되어 있다.

 

  1. template<class _Ux>
  2. explicit shared_ptr(_Ux *_Px)
  3. {       // construct shared_ptr object that owns _Px
  4.         _Resetp(_Px);
  5. }
  6.  
  7. template<class _Ux, class _Dx>
  8. shared_ptr(_Ux *_Px, _Dx _Dt)
  9. {       // construct with _Px, deleter
  10.         _Resetp(_Px, _Dt);
  11. }
  12.  
  13. template<class _Ux, class _Dx, class _Alloc>
  14. shared_ptr(_Ux *_Px, _Dx _Dt, _Alloc _Ax)
  15. {       // construct with _Px, deleter, allocator
  16.         _Resetp(_Px, _Dt, _Ax);
  17. }

 

두 번째 생성자의 정의부터 보이는 class _Dx를 우리가 정의한 클래스로 지정시, 

이는 shared_ptr의 참조 카운트가 0 이 될 때의 deleter 클래스가 된다.

 

예제 1) 배열 타입의 deleter

 

  1. // deleter 클래스 정의
  2. template<typename T>
  3. struct ArrayDeleter
  4. {      
  5.         void operator () (T* p)
  6.         {
  7.                 delete [] p;
  8.         }
  9. };
  10.  
  11. // shared_ptr 생성시 두 번째 인자로 deleter class를 넘기면...
  12. // 아무런 문제없이 객체 배열도 제대로 delete [] 처리가 된다.
  13. std::shared_ptr<int> spi( new int[1024], ArrayDeleter<int>() );

 

예제 2) Empty deleter

 

 

7) 참조 객체 형변환

 

shared_ptr 비멤버 함수를 통해 shared_ptr이 참조하고 있는 객체의 형 변환을 수행할 수 있다.

 

(참고로, shared_ptr의 모든 operator 연산자 역시 이처럼 비멤버 함수로 구현되어 있다)

 

  1. template<class _Ty1, class _Ty2>
  2. shared_ptr<_Ty1> static_pointer_cast(const shared_ptr<_Ty2>& _Other)
  3. {      
  4.         // return shared_ptr object holding static_cast<_Ty1 *>(_Other.get())
  5.         return (shared_ptr<_Ty1>(_Other, _Static_tag()));
  6. }
  7.  
  8. template<class _Ty1, class _Ty2>
  9. shared_ptr<_Ty1> const_pointer_cast(const shared_ptr<_Ty2>& _Other)
  10. {      
  11.         // return shared_ptr object holding const_cast<_Ty1 *>(_Other.get())
  12.         return (shared_ptr<_Ty1>(_Other, _Const_tag()));
  13. }
  14.  
  15. template<class _Ty1, class _Ty2>
  16. shared_ptr<_Ty1> dynamic_pointer_cast(const shared_ptr<_Ty2>& _Other)
  17. {      
  18.         // return shared_ptr object holding dynamic_cast<_Ty1 *>(_Other.get())
  19.         return (shared_ptr<_Ty1>(_Other, _Dynamic_tag()));
  20. }

 

예제)

 

  1. class Car {...};
  2. class Truck : public Car {...};
  3.  
  4. // Truck 타입의 객체를 Car 타입의 객체를 참조하는 shared_ptr에 초기화
  5. shared_ptr<Car> pCar( new Truck() );
  6.  
  7. // shared_ptr<Car>가 참조하고 있던 객체를 Truck 타입으로 static_cast하여 대입.
  8. // 대입 하였기에 참조 카운트는 '2'
  9. shared_ptr<Truck> pTruck = static_pointer_cast<Truck>(pCar);
  10.  
  11. // 위처럼 대입하지 않고 스스로 형변환만 하여도 상관없음.
  12. // 참조 카운트는 당연히 변화가 없다.
  13. static_pointer_cast<Car>(pCar);

 

 

8) 참조 객체 접근

 

shared_ptr이 참조하는 실제 객체를 얻는 방법은 명시적/암시적의 두 가지 방법이 있다.

 

명시적 방법

  • shared_ptr::get()
    : 참조하고 있는 객체의 주소를 반환한다.

 

암시적 방법

  • shared_ptr::operator*
    : 참조하고 있는 객체 자체를 반환한다.
    : 즉, *(get())의 의미
     
  • shared_ptr::operator->
    : get()->의 의미가 같다.

 

예제)

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

 

  1. shared_ptr<Car> spCar( new Truck() );
  2.  
  3. // spCar가 참조하는 객체의 주소를 반환
  4. Car* pCar = spCar.get();
  5.  
  6. // spCar가 참조하는 객체의 메써드에 접근 #1
  7. spCar.get()->MemberFunc();
  8.  
  9. // spCar가 참조하는 객체의 메써드에 접근 #2
  10. *(spCar).MemberFunc();
  11.  
  12. // spCar가 참조하는 객체의 메써드에 접근 #3
  13. spCar->MemberFunc();

 

 

9) Circular references

 

레퍼런스 카운팅 기반이기에 순환 참조에 대한 잠재적인 문제가 있을 수 있다.

즉, A와 B가 서로에 대한 shared_ptr을 들고 있으면 레퍼런스 카운트가 0이 되지 않아 메모리가 해제되지 않는다.

 

게임의 경우를 예로 들어, 파티 객체가 있으면 파티원 객체가 있을 것이다.

파티 객체는 파티원 객체를 리스트 형태로 들고 있고, 또 파티원도 자기가 속한 파티 객체를 알기 위해 참조 형태로 들고 있는 예제를 살펴보자.

 

  1. #include <memory>    // for shared_ptr
  2. #include <vector>
  3.  
  4. using namespace std;
  5.  
  6. class User;
  7. typedef shared_ptr<User> UserPtr;
  8.  
  9. class Party
  10. {
  11. public:
  12.     Party() {}
  13.     ~Party() { m_MemberList.clear(); }
  14.  
  15. public:
  16.     void AddMember(const UserPtr& member)
  17.     {
  18.         m_MemberList.push_back(member);
  19.     }
  20.  
  21. private:
  22.     typedef vector<UserPtr> MemberList;
  23.     MemberList m_MemberList;
  24. };
  25. typedef shared_ptr<Party> PartyPtr;
  26.  
  27. class User
  28. {
  29. public:
  30.     void SetParty(const PartyPtr& party)
  31.     {
  32.         m_Party = party;
  33.     }
  34.  
  35. private:
  36.     PartyPtr m_Party;
  37. };
  38.  
  39.  
  40. int _tmain(int argc, _TCHAR* argv[])
  41. {
  42.     PartyPtr party(new Party);
  43.  
  44.     for (int i = 0; i < 5; i++)
  45.     {
  46.         // 이 user는 이 스코프 안에서 소멸되지만,
  47.         // 아래 party->AddMember로 인해 이 스코프가 종료되어도 user의 refCount = 1
  48.         UserPtr user(new User);
  49.  
  50.         // 아래 과정에서 순환 참조가 발생한다.
  51.         party->AddMember(user);
  52.         user->SetParty(party);
  53.     }
  54.  
  55.     // 여기에서 party.reset을 수행해 보지만,
  56.     // 5명의 파티원이 party 객체를 물고 있어 아직 refCount = 5 의 상태
  57.     // 따라서, party가 소멸되지 못하고, party의 vector에 저장된 user 객체들 역시 소멸되지 못한다.
  58.     party.reset();
  59.  
  60.     return 0;
  61. }

 

위와 같은 형태로 shared_ptr이 서로를 참조하고 있는 것은 circular reference라고 한다.

 

위 예제처럼, 그룹 객체 - 소속 객체간 상호 참조는 실무에서도 흔히 볼 수 있는 패턴이며, 

보통은 위 예제처럼 직접 참조 형식이 아니라, User는 PartyID를 들고 있고, Party 객체에 접근 필요시 PartyManger(컬렉션)에 질의해서 유효한 Party 객체 포인터를 얻어오는 방식을 사용한다.

 

그렇다고, PartyManager에 일일히 ID로 검색하는 비용을 줄이고자, Party 포인터를 직접 들고 있으면, 들고 있던 포인터가 dangling pointer가 될 수 있는 위험이 있다.

 

이럴 때, User 객체가 Party 객체를 shared_ptr가 아닌 weak_ptr을 사용하여 들고 있다면, 검색 비용 회피와 dangling pointer의 위험에서 모두 벗어날 수 있다.

 

std::weak_ptr은 shared_ptr로부터 생성될 수 있고, shared_ptr이 가리키고 있는 객체를 똑같이 참조하지만, 참조만 할 뿐 reference counting은 하지 않아 위 예제의 목적에 가장 바람직한 대안이 될 수 있다 할 수 있다.

 

 

10) 멀티 쓰레드 안정성

 

MSDN에 보면 shared_ptr의 멀티 쓰레드 안정성에 대해 다음과 같이 얘기하고 있다.

 

Multiple threads can simultaneously read and write different shared_ptr objects, even when the objects are copies that share ownership.

 

하지만, shard_ptr의 내부 소스를 아무리 뒤져봐도 reference count에 대한 동기화는 보장이 되나, 참조하고 있는 객체에 대한 동기화 보장에 대한 내용은 없다.

 

이에 마침 검색을 해보니 다음 블로그 링크를 찾게 되었다.

http://process3.blog.me/20049917212

 

즉, 결론만 이야기하면, 레퍼런스 카운트에 대해서만 동기화를 해서 멀티 쓰레드에서의 안정성을 얻는다.


11) 적절한 활용 예시

포인터를 담는 벡터에 대한 내용인데, 괜찮아서 링크 건다.

http://sanaigon.tistory.com/72

 

 

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

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

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

 

 

출처: http://sanaigon.tistory.com/72

 

[TR1 라이브러리 #2] 메모리 릭 걱정이 없는 shared_ptr

 

shared_ptr란 무엇인가?

TR1라이브러리에는 STL의 auto_ptr과 비슷한 스마트 포인터가 있다 바로 shared_ptr다. 
이것은 템플릿 클래스 형태로 특정한 자료형 형식을 지정해 줄 경우 그 자료형을 가리킬수 있는 포인터를 말한다. 예를 들어 int형를 가리키는 포인터를 만들기 위해 보통 다음과 같이 " int * "을 쓸 것이다. 하지만 이런 보통 포인터에 메모리를 힙 영역에 할당 하는 경우 반드시 delete하지 않으면 프로그램상에서 메모리 릭이 발생하게 된다. 

int *p = new int(3)
사용후 반드시 delete p;


경험이 있는 프로그래머라면 이런 위험 요소들을 잘 처리하는 것에대한 중요성을 잘 알고 있을 것이다. 아무리 조심을 한다고는 하지만 프로그래머 또한 인간이기에 가끔 이런 실수를 할 수도 있다. 이런 메모리 릭은 에러가 아니기때문에 컴파일러에서 걸를수도 없고 발견하기도 어렵다. 

이런것들을 막기위해 TR1라이브러리에서는 shared_ptr이라는 것이 존재한다. 이 포인터는 스마트 포인터의 일종으로서 메모리 해제하는 작업을 포인터 자체가 알아서 해주는 기능이 숨어 있다. 이외에 reference counter(참조횟수)도 관리하고 있어서 shared_ptr객체를 복사하면 참조횟수가 증가하고, shared_ptr객체를 파괴하면 참조횟수는 감소 한다. 이렇게 해서 reference counter가 0 이되는 순가 shared_ptr가 소유하고 있는 자료형의 자원(메모리)는 자동 해제 되게된다. 사용 예는 다음과 같다 

shared_ptr<int> sp(new int());

위는 힙 영역에 int형 만큼 할당된 메모리공간을 가리키고 있는 int형의 shared_ptr객체의 선언이다. 
shared_ptr의 객체인 sp는 'int*'와 같이 '->'연산자나  '*'연산자를 통해 메모리 공간에 접근이 가능하고 따로 메모리해제를 할 필요 없이 shared_ptr 객체가 소멸되는 순간 메모리는 자동 해제하게 된다. 

shared_ptr의 사용예

흔히 망각하기 쉬운 예로 shared_ptr의 강점을 알아보도록 하겠다. 흔히 STL의 vector컨테이너에 특정 자료형에 대한 포인터를 넣어서 사용하는 경우가 있다. 이런 경우 shared_ptr를 vector와 함께 사용할 경우 꽤 편리 할 수 있다. 다음의 예를 보자.
 

 

 

 


위의 경우 흔히 하기 쉬운 실수중에 하나로 vector에 pushback할때 하나의 Test의 객체를 생성해서 그 객체의 포인터를 벡터에 넣고 있다. 하지만 이는 프로그램이 끝나는 시점에서 각 컨테이너 요소의 포인터들을 직접 메모리 해제 하지 않는 경우 위의 실행 창과 같이 생성자만 불리고 소멸자는 불리지 않게 되는 문제점이 있다. 

그렇다면 vector가 포인터를 가지고 있는것이 아니라 객체를 가지고 있다면 어떻게 될까?

 

 

 


위의 보기는 생성자와 소멸자가 정신없게 불린 상황이다. 객체의 생성은 이 전의 예와 같이 동적으로 메모리를 할당해 생성하였지만 객체의 포인터가 아닌 포인터가 가리키고 있는 객체자체를 벡터에 넣고 있다. pushback하는 동안 STL자체는 call by value에 의해 복사가 되므로 클래스 내에 복사생성자가 잘 구현되어야 있어야 복사가 깊은 복사로 잘 이뤄지며 pushback을 하는 동안 벡터내의 insert과정에서 소멸자가 불리게 되어 각 루프를 돌때마가 소멸자가 그 숫자에 맞게 불리게 된다. 그리고 마지막으로 벡터 객체가 소멸하는 순간 객체에 대한 소멸자를 자동으로 불러 다시 한번 소멸자가 불리게 되어 위와 같이 어지럽게 생성과 소멸이 반복되고 있다. 

그렇다면 shared_ptr를 사용한 벡터는 어떤 모습일까?

 

 

 

shared_ptr를 객체로 가지고 있는 벡터 컨테이너의 객체의 경우는 메모리 해제작업을 shared_ptr자체가 하기 때문에 프로그램이 끝나는 부분에서 메모리 해제 작업을 깜박하고 하지 않는다 하더라도 자동적으로 메모리가 해제되게 된다. 그로인해 위와 같이 깔끔하게 생성자 5번 소멸자 5번 씩 짝을 이루며 메모리 릭이 방지되는 것이다. 
이처럼 포인터를 벡터에 담는 경우는 가급적 shared_ptr을 사용한다면 메모리릭 방지에 많은 도움이 될 것이다. 

 

 

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

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

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

 

 

출처: http://vallista.tistory.com/entry/C-11-Smart-Pointer-Sharedptr-Uniqueptr-Weakptr

 

이미 이 전에 스마트 포인터 기능으로 auto_ptr이 존재를 하였다.

하지만, auto_ptr은 포인터의 소유권 문제가 있었다. (쓰레기 취급 받으며 안썼음)

그래서 스마트 포인터를 사용을 아에 안하거나 boost 라이브러리의 스마트 포인터를 사용했었다. (shared_ptr)

 

auto_ptr에서 소유권 문제가 발생하는 이유는 

복사 생성자와 할당 연산자 구현이 멤버 데이터에 대한 깊은 복사 대신 얕은 복사를 하도록 되어 있기 때문이다.

그래서 함수안으로 온전한 auto_ptr이 전달되지 않으며 auto_ptr을 전달하면 복사 생성자가 호출되고 그 결과 얕은 복사가 발생하기 때문이다. (얕은 복사와 깊은 복사의 개념을 잘 모르면 공부를 해야 한다.)

하지만 이런 auto_ptr도 문제점 투성이인 것 만은 아니고 얕은 복사를 하는 특성 덕택에 특정 순간 객체의 소유권이 유일하게 하나의 auto_ptr 객체에만 존재하게 되었다.

 

하지만 auto_ptr 객체 자체는 복사가 필요한 곳에 사용을 할 수 없다는 모든 장점을 다 가리는 단점이 존재해서 새로운 스마트 포인터가 대두되기 시작했다.

 

기존의 C++ 표준은 복사 생성자나 할당 연산자를 통해 Copy Semantics 를 지원했다. (Copy Semantics, Move Semantics를 모르는 먼저 Copy Semantics를 공부하길 .. Move Semantics는 앞으로 계속 알아갈 예정)

 

이는 사용자가 클래스안에 복사 생성자나 할당 연산자를 별도로 구현하지 않아도 언어 차원에서 시본으로 지원해주는 객체 복사의 개념이다. 해당 객체가 클래스 멤버로 포인터 타입의 데이터를 갖는 경우라면, 컴파일러가 만들어내는 복사 생성자와 할당 연산자에 의지하는 대신, 프로그래머가 직접 구현하여 컴파일러에 의해 생성되는 복사 생성자와 할당 연산자를 오버로딩 해야한다고 알고 있을텐데, 그 이유는 앞에서도 언급한 바와 같이 얕은 복사의 문제를 극복하려는 방법이다.

 

새로운 C++ 11 표준에서는 Copy Semantics에 추가로 새로운 개념인 Move Semantics가 등장하였다.

Move Semantics는 Copy Semantics 처럼 두 객체 사이에 복사를 수행하는 대신 객체의 데이터 필드 하나 하나를 이전 시키는 역할을 수행 했음. (Move Semantics를 자세히 언급안하는 이유는 언급하게되면 끝이 없기 때문에 위에도 말했다시피 천천히 알아 갈 것임)

 

어떤 객체를 생성해야 할 때는 일반적으로 클래스의 생성자를 호출해 만드는데, 때로는 이미 만들어진 객체의 복사는 할당을 통해서 새로운 객체를 만들기도 한다. 다른 예로 STL의 백터나 리스트와 같은 컨테이너의 경우 이들은 일종의 동적 배열이기 때문에 그 크기가 상황에 따라 두 배씩 늘어나게 된다.

이때 메모리 내부에서는 대량의 복사가 발생한다. 그런데 STL 컨테이너에서의 문제는 복사후 원본과 사본 모두를 사용하는게 아니라 원본 파괴하고 사본만 사용함.

단지 배열의 크기를 늘리기 위해 불필요한 복사 동작을 하여 오버헤드를 내게되는데, 이로인해 쓸데없는 객체를 생성하거나 사용하지 않는 원본 객체를 파괴한다는 점 등 이런 일련의 불필요한 동작은 C++ 성능저하의 주범이라는 고질적인 문제점이 있었음.

 

그래서 이를 위해서 복사보다는 이동이 낫겠다고 판단하여 Move Semantics를 도입하게 됨.

이를 위해 이동 생성자 라는 개념을 도입함.

 

그래도 문제가 남아 있었는데, Move Semantics 라는 개념을 스마트 포인터에도 적용하려니 auto_ptr의 내부 구현이 이동 시맨틱을 지원할 수 있도록 업그레이드 하기엔 기술적 제약이 걸려있고 이미 사용되던 기존 auto_ptr에 대한 호환성을 해칠수도 없었기 때문에 unique_ptr이라는 이름의 새로운 단일 포인터 타입을 구현하게 됨.

C++ 11 에서는 auto_ptr은 deprecated 됨 

unique_ptr 내부에는 복사 생성자와 할당 연산자가 아에 구현되어 있지 않는다.

따라서 우리가 기존에 알던 복사 생성자나 할당 연산자를 작성하지 않아도 컴파일러가 이를 기본적으로 제공 안해준다.

Unique_ptr은 객체가 복사가 원천 봉쇄되어 있어 이동만 가능하다.

이동도 std::move를 사용해서 이용 가능하다.

 

이제 소스를 보도록 하자.

 

Unique_ptr

 

1번 예제

 

#include <iostream> // 기본적인 C++ 문법을 사용하기 위한 포함 헤더

#include <memory> // memory 함수를 사용해야 함.

#include <string> // 문자열 함수를 사용해야 함.

 

class Person

{

public:

     Person() {};

     Person(int age, std::string name) : age(age), name(name) {}

     ~Person() {};

 

public:

     int GetAge() { return age; }

     std::string GetName() { return name; }

 

private:

     int age;

     std::string name;

};

 

int main(int argc, char** argv)

{

    std::unique_ptr<int> p1(new int(5)); // p1이라는 이름으로 단일 포인터 만들고 5라는 값으로 초기화

    //std::unique_ptr<int> p2 = p1;       // 단일 포인터는 복사 허용안함, 주석 해제하면 컴파일 에러 발생한다.

    std::unique_ptr<int> p3 = std::move(p1); // std::move() 함수를 통해 p1이 가졌던 메모리 영역을 p3에게 transfor 함, 이 동작으로 인해 p1은 더이상 존재 안함.

    p3.reset();                                    // reset 함수를 통해 p3가 가졌던 메모리 영역 초기화 가능. p3를 nullptr로 만드는 것과 같은 효과가 있음. 스마트 포인터는 명시적인 메모리 해제의 필요가 없으나 API를 소개하려고 작성했다.

    p1.reset();                                    // p1은 이미 존재하지 않는 포인터이므로 아무 효과도 발생하지 않음.

 

    return 0;

}

 

2번 예제

 

#include <iostream>

#include <memory>

#include <string>

 

class Person

{

public:

     Person() {};

     Person(int age, std::string name) : age(age), name(name) {}

     ~Person() {};

 

public:

     int GetAge() { return age; }

     std::string GetName() { return name; }

 

private:

     int age;

     std::string name;

};

 

int main(void)

{

   std::unique_ptr<int> p1(new int(5));       // 정수 5를 가리키는 포인터 타입 p1을 만듬

   std::unique_ptr<int> p3 = std::move(p1);   // move() 함수를 이용하며 p1의 소유권을 p3로 이전

 

    // get() 함수를 이용하면 포인터의 주소 값을 얻을 수 있음.

   std::cout << p1.get() << std::endl;          

   std::cout << p3.get() << std::endl;          // 주소 반환

 

   std::cout << *p3 << std::endl;               // dereferencing를 통해 포인터가 가리키는 값을 가져옴

   auto a = *p3;                                // C++11의 auto 키워드를 통해 값 할당

   std::cout << a << std::endl;

   auto& a2 = p3;                               // auto 키워드는 값을 할당 받을떄도 사용 가능

   std::cout << *a2 << std::endl;

 

   // p1이 가리키는 값은 이미 소유권이 p3로 이전되었기 때문에 p1을 역참조 하려고 하면 런타임에러가 발생

   //auto b = *p1;

   //std::cout << b << std::endl;

 

   p3.reset();  // 아무것도 안함

   p1.reset();  // 아무것도 안함

    

    return 0;

}

 

3번 예제

 

#include <iostream>

#include <memory>

#include <string>

 

class Person

{

public:

     Person() {};

     Person(int age, std::string name) : age(age), name(name) {}

     ~Person() {};

 

public:

     int GetAge() { return age; }

     std::string GetName() { return name; }

 

private:

     int age;

     std::string name;

};

 

int main(void)

{

    // Unique_ptr 은 auto_ptr을 계승하였기 때문에 new로 메모리 할당을 해도 delete로 안지워줘도 된다. (자동 소멸된다)

    std::unique_ptr<Person> p (new Person(1, "baby");

    std::cout << "Name : "<< p->GetName() << std::endl;

    std::cout << "Age : " << p->GetAge() << std::endl;

     

    getchar();

     

    return 0;

}

 

Shared_ptr

 

Shared_ptr은 Unique_ptr과 틀리게 공유가 가능한 스마트 포인터다.

즉 객체 소유권을 이곳 저곳에서 관리가능하다는 것 이다.

Shared_ptr은 이곳 저곳에서 객체의 소유권을 가지고 있기 때문에 객체의 소유권이 올바르게 회수되었는지를 확인하기 위해 Reference Counting 기법을 사용한다. 

Reference Counting 기법을 사용하므로써 객체가 몇번이나 복사 되었는가 또는 새롭게 복사될 때마다. Count를 증가시키므로써 객체가 몇개 있는지를 파악할 수 있고, 객체를 해제하면 그만큼의 레퍼런스 카운트를 줄인다.

 

예제

 

#include <iostream>

#include <memory>

#include <string>

 

class Person

{

public:

     Person() {};

     Person(int age, std::string name) : age(age), name(name) {}

     ~Person() {};

 

public:

     int GetAge() { return age; }

     std::string GetName() { return name; }

 

private:

     int age;

     std::string name;

};

 

int main(void)

{

    std::shared_ptr<int> p1(new int(5));

    std::shared_ptr<int> p2 = p1;

  

    p1.reset();

    p2.reset();

 

    return 0;

}

 

Weak_ptr

 

Shared_ptr은 Reference Counting 기법으로 인해 실제 메모리가 몇번이나 복사되어 사용되는지 내부적으로 추적하기위해 레퍼런스 카운팅 방식을 이용했다. 하지만 이 레퍼런스 카운팅의 잠재적인 위험 가운데 하나로 서로를 참조하는 순환참조 (Circular Reference -> A는 B를 가리키고 B는 A를 가리키는 상황) 가 될 위험이 있기 때문에 이런 상황에서 순환 참조에 참여하는 모든 인스턴스가 삭제될 수 있으며, 이는 곧장 메모리 누수로 이어지는 괴랄한 상황이 발생하게 된다.

바로 이런 Shared_ptr의 문제를 해결하는 것이 Weak_ptr 이다.

 

Shared_ptr 에서는 메모리를 참조하는 Shared_ptr이 자신을 제외하고 하나라도 남아 있으면, 아무리 삭제 명령을 내려도 해당 메모리가 삭제되지 않는다. 해당 메모리를 가리키는 포인터 타입이 Shared_ptr이 아닌 weak_ptr이면 해당 메모리 삭제가 가능하다.

weak_ptr이 가리키는 메모리 공간은 shared_ptr이 메모리를 관리하려고 사용하는 레퍼런스 카운트에 포함되어있지 않기때문에 순환 참조가 일어날 수 없다.

 

예제

 

#include <iostream>

#include <memory>

#include <string>

 

class Person

{

public:

     Person() {};

     Person(int age, std::string name) : age(age), name(name) {}

     ~Person() {};

 

public:

     int GetAge() { return age; }

     std::string GetName() { return name; }

 

private:

     int age;

     std::string name;

};

 

int main(void)

{

    // weak_ptr은 shared_ptr이 아직 존재하는지 여부를 확인해볼 때 사용할 수 있다.

    std::shared_ptr<int> sp1(new int(5));                  // Shared_ptr로 5의 변수를 할당

    std::weak_ptr<int> wp1 = sp1;                           // Weak_ptr 포인터 변수 생성해 sp1을 참조한다. (wp1도 sp1과 같이 18번지 가리키지만 18번지 소유권은 sp1에게 있음)

    {

         std::shared_ptr<int> sp2 = wp1.lock();           // SP1은 SP1으로 SP2를 복사한다. 약한 포인터의 멤버 함수 lock()을 이용해 wp1이 가리키던 공유 포인터를 반환하도록 하고, 이를 새로운 공유 포인터인 sp2가 받는다.

                                                                          // 이제 18은 sp1과 sp2의 공동 소유이며 레퍼런스 카운트는 2가 된다.

 

         if(sp2)

         {

              std::cout << "sp2 has copy of sp1" << std::endl;             // sp2가 있으므로 출력 구문 실행

         }

    }         // sp2는 범위 연산자({}) 를 만나는 순간 sp2는 생성된 범위를 벗어나게 된다. 스마트 포인터이므로 명시적으로 delete를 호출해 줄 필요가 없다. 이제 sp2가 파괴됨으로써 레퍼런스 카운트는 -1이 되어 1이 되었다.

 

    sp1.reset(); // sp1은 이곳에서 파괴한다. 여기서 Reference Count가 2면 -1을 시키고, 1이면 -1을 하여 0이된다. 0이 되면 메모리를 해제하게 되고 18번지는 이제 아무도 소유하지 않는다.

    std::shared_ptr<int> sp3 = wp1.lock(); // wp1이 가리키던 공유 포인터를 반환하여 sp3에 복사한다. 하지만 약한 포인터의 특징상 자신이 가리키던 공유 포인터의 리소스가 파괴되면 자신도 자동으로 파괴되는 특징이 있으므로 lock 멤버 함수를 호출하면 빈 shared_ptr이 반환된다.

     

    if (sp3)

    {

         std::cout << "sp3 has copy of sp1" << std::endl;            // sp3는 위에서 sp1을 reset 해줌으로써 레퍼런스 카운트가 0 이므로 자동해제되어 있기 때문에 출력이 되지 않는다.

    }

   

    return 0;

}

 

이제 멀티 쓰레드를 생각하는 사람이라면 동시성 문제를 생각을 하게 될 것이다.

하지만 shared_ptr과 Weak_ptr는 레퍼런스 카운팅 방식이므로 안전하게 사용할 수 있다.

단 안전하게 사용할 수 있는 경우는 각 쓰레드가 내부적으로 각각 공유 포인터가 있고 공유포인터가 동일한 리소스에 접근하는 경우이다.

하지만 각 쓰레드가 쓰레드 외부에 있는 하나의 shared_ptr 객체에 접근한다면 C++11이 제공하는 다른 원자 함수들을 사용해야한다.

 

 

출처: http://vallista.tistory.com/entry/C-11-Smart-Pointer-Sharedptr-Uniqueptr-Weakptr [VallistA]

 

 

 

 

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

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

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

 

 

출처: http://psychoria.tistory.com/entry/C-11-%EC%83%88%EB%A1%9C%EC%9A%B4-%EC%8A%A4%EB%A7%88%ED%8A%B8-%ED%8F%AC%EC%9D%B8%ED%84%B0-uniqueptrautoptr%EC%9D%98-%EB%8C%80%EC%B2%B4

 

 

C++11에서 auto_ptr이 사라지고 unique_ptr이 새로 추가되었습니다.

 

auto_ptr의 문제점에 대해서는 아래의 글을 확인하시면 됩니다.

 

2014/12/08 - [Programming/C&C++] - 스마트하지 못한 스마트한 포인터 auto_ptr

 

auto_ptr을 대체하는 unique_ptr에 대해서 알아보도록 하겠습니다.

 

unique_ptr은 auto_ptr과 거의 유사한 멤버를 가지고 있습니다.

 

동적 할당된 포인터를 받아서 해당 포인터를 핸들링하고 자동으로 메모리를 해제하는 역할까지 동일합니다.

 

* 연산자나 -> 연산자도 auto_ptr과 동일하게 지원합니다.

 

unique_ptr을 생성하는 코드를 보도록 하겠습니다.

 

#include <string>

#include <memory>

#include <iostream>

 

struct TestStuff

{

    TestStuff() : nIntVal(0), szVal(){};

    TestStuff(int nValue, std::string szValue) : nIntVal(nValue), szVal(szValue){};

 

    int nIntVal;

    std::string szVal;

};

 

int main()

{

 

    // Make unique_ptr variable

    std::unique_ptr<TestStuff> ptr_Temp1(new TestStuff{ 3, "Hello" });

    std::cout << ptr_Temp1->nIntVal << " and " << ptr_Temp1->szVal << std::endl;

 

    // Make unique_ptr variable using std::make_unique

    std::unique_ptr<TestStuff> ptr_Temp2 = std::make_unique<TestStuff>(4, "Hi");

    std::cout << (*ptr_Temp2).nIntVal << " and " << (*ptr_Temp2).szVal << std::endl;

 

    return 0;

}

std::unique_ptr<T> 의 형식으로 변수를 선언하고 new로 메모리를 할당해서 생성이 가능합니다.

 

또한 C++ 14에서 새로 추가된 std::make_unique()를 사용해서 생성도 가능합니다.

 

물론 C++ 14를 지원하는 컴파일러로 작업을 해야 합니다.

 

ptr_Temp1과 ptr_Temp2가 생성이 됩니다.

 

unique_ptr을 사용하는 방법은 다음과 같습니다.

 

int main()

{

    // Make unique_ptr variable

    std::unique_ptr<TestStuff> ptr_Temp1(new TestStuff{ 3, "Hello" });

    std::cout << ptr_Temp1->nIntVal << " and " << ptr_Temp1->szVal << std::endl;

 

    // Make unique_ptr variable using std::make_unique

    std::unique_ptr<TestStuff> ptr_Temp2 = std::make_unique<TestStuff>(4, "Hi");

    std::cout << (*ptr_Temp2).nIntVal << " and " << (*ptr_Temp2).szVal << std::endl;

 

    ptr_Temp1.reset(); // ptr_Temp1 is nullptr.

    if (ptr_Temp1) // operator bool

        std::cout << ptr_Temp1->nIntVal << " and " << ptr_Temp1->szVal << std::endl;

    else

        std::cout << "Empty" << std::endl;

 

    ptr_Temp1.reset(new TestStuff{ 5, "Bye" }); // ptr_Temp1 is assigned.

 

    TestStuff* pStuff = ptr_Temp2.release(); // ptr_Temp2 is nullptr.

    if (ptr_Temp2)

        std::cout << ptr_Temp2->nIntVal << " and " << ptr_Temp2->szVal << std::endl;

    else

        std::cout << "Empty" << std::endl;

 

    delete pStuff; // must delete assigned memory.

 

    // Must use std::move() for move pointer.

    ptr_Temp2 = std::move(ptr_Temp1); // ptr_Temp1 is nullptr, ptr_Temp2 is 5, "Bye"

 

    if (ptr_Temp2)

        std::cout << ptr_Temp2->nIntVal << " and " << ptr_Temp2->szVal << std::endl;

    else

        std::cout << "Empty" << std::endl;

 

    return 0;

}

reset()은 내부에 있는 포인터의 메모리를 해제하고 nullptr로 변경합니다.

 

reset()을 사용할 때 reset(new TestStuff{...}) 등으로 사용하면 새로운 메모리 할당이 가능합니다.

 

또한 unique_ptr은 그 자체로 bool연산이 가능해서 nullptr일 경우 false를 리턴합니다.

 

또한 release() 멤버 함수를 사용해서 unique_ptr 내부의 포인터를 다른 포인터로 옮기는 것도 가능합니다.

 

이 때는 반드시 받은 포인터의 메모리를 해제해 줘야 합니다.

 

또한 = 연산자로 auto_ptr과 동일하게 포인터를 옮길 수 있지만 std::move()를 사용해서 옮겨야 합니다.

 

아래와 같은 방법을 사용하면 동적 할당된 배열에 대해서도 처리가 가능합니다.

 

1

std::unique_ptr<int[]> ptr(new int[4]{1, 2, 3, 4});

배열에 대한 deleter가 구현이 되어 있기 때문에 int[] 등으로 사용하게 되면 내부적으로는 delete[]가 호출이 됩니다.

 

마지막으로 unique_ptr은 아래와 같이 deleter를 직접 구현하는 것도 가능합니다.

 

struct ArrayDeleter

{

    template <class T>

    void operator()(T* p)

    {

        delete[] p;

    }

};

 

int main()

{

    std::unique_ptr<int[], ArrayDeleter> ptr(new int[4]{1, 2, 3, 4});

}

 

STL의 functor를 활용해서 delete를 직접 구현하고 변수 선언할 때 넣어줄 수 있습니다.

 

이렇게 하면 메모리를 동적 할당하는 방법에 제약이 생기지 않습니다.

 

또한, functor를 활용하면 내부에서 추가적으로 할 동작을 정의할 수 있는 장점이 있습니다.

 

메모리 누수의 걱정이 생길 때는 unique_ptr를 활용해서 개발을 하시면 큰 도움이 될 것입니다.

 

unique_ptr의 설명을 마치겠습니다.

 

 

 

출처: http://psychoria.tistory.com/entry/C-11-새로운-스마트-포인터-uniqueptrautoptr의-대체 [냉정과 열정사이]

 

 

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

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

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

 

 

출처: http://pacs.tistory.com/entry/%ED%95%AD%EB%AA%A9-13-%EC%9E%90%EC%9B%90-%EA%B4%80%EB%A6%AC%EC%97%90%EB%8A%94-%EA%B0%9D%EC%B2%B4%EA%B0%80-%EA%B7%B8%EB%A7%8C-sharedptr-autoptr

 

view plaincopy to clipboardprint?

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Widget{  
  5. public:  
  6.     Widget(){cout<<" Widget() "<<endl;}  
  7.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  8.     void dosomething() {}  
  9. };  
  10.   
  11. void main()  
  12. {  
  13.     Widget* p = new Widget;  
  14.       
  15.     //자원을 이용하는 코드...  
  16.       
  17.     delete p;  
  18. }  

 이 책에서는 자원을 동적으로 할당된 객체에 대해서만 한정해 설명을 하고 있는데, 예를 들어 Widget* p = new Widget(); 와 같은 코드에서 p 자체를 자원이라고 부르고 있습니다. C++은 delete로 자원을 반납하는 특성을 가집니다. 동적으로 할당된 자원을 delete로 반납하지 않으면 메모리 누수가 일어나죠. 애초에 작성할 때 new로 할당을 하고 이어서 delete를 해주고 그 사이에 코드를 작성하게 되는데, 이 사이에 들어가는 코드가 return 문을 가지고 있는 경우에 delete문을 호출해 주지 않기 때문에 마지막에서 delete를 해준다고 해도 중간에 함수가 끝나서 메모리 누수가 발생합니다. 만약 이런 코드가 루프 문에 들어가 있다고 해봅시다.

view plaincopy to clipboardprint?

  1. for(..)  
  2. {  
  3.     Widget* p = new Widget;  
  4.     break;   
  5.     continue;  
  6.   
  7.     delete p;  
  8. }  

 루프문은 break이나 continue를 이용해서 루프를 제어하게 되는데 이런 제어 코드 때문에 위 경우와 마찬가지로 delete가 실행이 되지 않고 메모리 누수가 일어 날 수 있습니다. 만약 이런 상황을 막기 위해서는 제어 코드 이전에 delete를 해주면 되지만, 이런 코드 들이 여러개 존재하고 이런 일들을 일일이 해준다고 한다면 번거롭기도 하고, 자원관리에 어려움이 있습니다. 이런 자원을 사용자가 직접 할당 받고 반납하는 과정을 다 염두에 두고 코딩을 해야 하는 것이 프로그래머에게 부담이 되고 문제가 됩니다. 그래서 이펙티브 C++에서는 자원을 사용자가 직접 해제 하지 말고, 자원 관리 객체를 이용해 그 자원관리 객체가 소멸할때 소멸자에서 자원을 소멸하도록 이용하라고 되어 있습니다. 

 

 auto_ptr  

 자원관리 객체는 주로 스마트 포인터를 이용하는데, 스마트 포인터 중에 첫번째로는 Auto Pointer가 있습니다. 이것은 memory 헤더 안에 선언이 되어 있어 사용할때는 헤더를 선언해주고 사용해줘야 합니다. 

view plaincopy to clipboardprint?

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4.   
  5. class Widget{  
  6. public:  
  7.     Widget(){cout<<" Widget() "<<endl;}  
  8.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  9.     void do() {}  
  10. };  
  11.   
  12. void main()  
  13. {  
  14.     std::auto_ptr<Widget> p(new Widget);  
  15. }  

 위와 같이 auto_ptr을 사용하면 예전처럼 동적 할당후 자원 반납을 해주지 않아도 자기 객체가 사라질때 소멸자에서 자원의 delete를 적용해줘서 결과적으로 delete를 안써줘도 되게 만들어져 있습니다. 여기 코드에서는 할당받은 자원을 자원 관리 객체 p에다가 넘기고 있는데, 자원관리 객체의 초기화 코드에 할당받은 자원을 넘기는 것을 "자원획득 초기화(RAII)" 라고 합니다.즉, 자원관리 객체 p가 있고 p의 초기화로 자원을 넘겨서 그 p가 소멸이 될때 그때 delete를 해주게 되는 방식이죠. 

 이제 return과 같은 제어코드나 다른 상황에서도 이 p는 지역변수 와 똑같이 동작을 해서 루프를 빠져나와 함수를 빠져나갈때 delete를 호출해 주기 때문에 자원누수를 막을 수 있습니다. 그러니까 자원해제를 사용자가 하는게 아니라 자원 관리 객체 라는 객체가 관리를 해주니까 누수에 대한 걱정을 하지 않아도 되는 것입니다. 

 

 그런데 auto_ptr에는 아래와 같은 문제가 일어날 수 있을 것이라고 생각할 수 있을것입니다.

view plaincopy to clipboardprint?

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4.   
  5. class Widget{  
  6. public:  
  7.     Widget(){cout<<" Widget() "<<endl;}  
  8.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  9.     void do() {}  
  10. };  
  11.   
  12. void main()  
  13. {  
  14.     std::auto_ptr<Widget> p(new Widget);  
  15.     std::auto_ptr<Widget> p2(p); //복사  
  16. }  

 위와 같이  auto_ptr을 복사를 하게 되면 똑같은 자원에 대해 두 자원관리 객체가 생기게 되는 꼴이 되는데, 이놈의 소멸자가 자원에 대한 delete를 해주게 되니까 같은 자원을 두번 해제하게 되는 일이 발생할 수도 있을 거라 예상이 될 것입니다. 그래서 auto_ptr은 그런것을 막기 위해서 소유권을 넘긴다는 방식을 취하고 있다. 

  소유권을 넘긴다? 소유권을 넘긴다는 의미는 기존의 자원은 null로 바꿔 버리고 새로 복사된 객체(이전에 있던 자원을) 유지하는 방식으로 동작을 합니다. 

view plaincopy to clipboardprint?

  1. void main()  
  2. {  
  3.     std::auto_ptr<Widget> p(new Widget); //Null  
  4.     std::auto_ptr<Widget> p2(p);         //Widget  
  5. }  

  C++에서도 'delete NULL' 해도 정상적으로 작동을 하므로 표준에 어긋난 것은 아닙니다. 여기에서 p2가 먼저 소멸 되는데, 그때 자원이 반납되고, p는 null이기 때문에 어떤 동작도 안하게 되므로, 자원 관리 객체의 복사 문제 해결하고 있습니다. 물론 복사대입 연산자도 이런 처리가 되어 있습니다. 하지만 문제가 있습니다.

view plaincopy to clipboardprint?

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4.   
  5. class Widget{  
  6. public:  
  7.     Widget(){cout<<" Widget() "<<endl;}  
  8.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  9.     void dosomething(){}  
  10. };  
  11.   
  12. void main()  
  13. {  
  14.     std::auto_ptr<Widget> p(new Widget);  
  15.     std::auto_ptr<Widget> p2(p); //복사  
  16.   
  17.     p2->dosomething(); //take breakpoint  
  18.     p->dosomething();  
  19. }  

 

 

 

 

 위 소스 코드를 실행해보면, p2는 지금 자원을 가지고 있기 때문에 정상적으로 호출되는데 p는 p2의 자원을 넘겨주고 자신은 null로 바뀌었기 때문에 null에 대해서 멤버 함수를 호출하는 동작을 취하면 프로그램이 이 부분에서 뻗게 되는 것입니다. 기존 동작의 포인터와 다른 복사 동작을 가지고 있는것이 오토 포인터의 특징이라고 할 수 있습니다. 

  하지만 이런 auto_ptr은 특성 때문에 STL의 컨테이너나 같은 곳에 같이 사용하면 문제가 발생하는 경우가 생길 수 있습니다. 그래서 우리가 일반적인 포인터를 사용할때처럼, 복사는 자유로우면서, 소멸 또한 관리가 되는 또 다른 자원관리 객체의 필요성이 대두 됩니다. 그래서 나타난것이 shared_ptr입니다. 

 

 shared_ptr  

 : shared_ptr은 참조 카운팅 방식 스마트 포인터(reference-counting smart pointer) 중의 하나로서, 원래는 Boost Library에 있다가 C++의 새로운 표준인 tr1이라는 이름공간에 추가되었습니다.

view plaincopy to clipboardprint?

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4.   
  5. class Widget{  
  6. public:  
  7.     Widget(){cout<<" Widget() "<<endl;}  
  8.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  9.     void dosomething(){}  
  10. };  
  11.   
  12. void main()  
  13. {  
  14.     std::tr1::shared_ptr<Widget> p (new Widget);  //counting : 1  
  15.     std::tr1::shared_ptr<Widget> p2(p);           //counting : 2  
  16.   
  17.     p2->dosomething();  
  18.     p->dosomething();  
  19. }  

 shared_ptr은 내부적으로 카운팅을 유지 해서(참조 갯수를 유지 해서) 자원의 참조 갯수를 셉니다. 처음 생성했을 때는 한개가 되고, 그 다음에는 두개가 되는 식으로 말이죠. shared_ptr는 참조 갯수만 관리 하기 때문에 위 소스코드가 무리없이 동작 되는 것을 알 수 있습니다. 그리고 shared_ptr도 마찬가지로 소멸시 delete를 호출해주는데 이 점때문에, 복사동작이 많이 요구되고 동시에 자원이 관리 되어야 하는 상황에서는 auto_ptr 보다 shared_ptr이 더 적합합니다. 그런데 문제는 자원이 동적으로 하나만 생성된것이 아니라, 아래와 같이 동적 배열로 생성된 경우는 shared_ptr은 스스로 판단을 할 수 없다는 것이 문제점 입니다.

view plaincopy to clipboardprint?

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4.   
  5. class Widget{  
  6. public:  
  7.     Widget(){cout<<" Widget() "<<endl;}  
  8.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  9.     void dosomething(){}  
  10. };  
  11.   
  12. void main()  
  13. {  
  14.     std::tr1::shared_ptr<Widget> p (new Widget[4]);  
  15.     std::tr1::shared_ptr<Widget> p2(p);             
  16.   
  17.     p2->dosomething();  
  18.     p->dosomething();  
  19. }  

 auto_ptr과  마찬가지로 shared_ptr도 delete 를 호출합니다. 하지만 위와 같이 동적 배열로 생성된 경우에는 delete[]로 호출해서 소멸시켜야 하는데, 그냥 delete만 호출 하는 할당과 반납의 형태가 다르다는 문제가 생기는 것입니다. 그래서 이런 경우는 STL의 벡터(vector) 같은 컨테이너와 같이 조합을 해서 이용하는 것을 추천합니다. 

view plaincopy to clipboardprint?

  1. #include <iostream>  
  2. #include <memory>  
  3. #include <vector>  
  4. using namespace std;  
  5.   
  6. class Widget{  
  7. public:  
  8.     Widget(){cout<<" Widget() "<<endl;}  
  9.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  10.     void dosomething(){}  
  11. };  
  12.   
  13. void main()  
  14. {  
  15.     std::vector<std::tr1::shared_ptr<Widget>> p(4);  
  16.     std::tr1::shared_ptr<Widget> p2(p);             
  17.   
  18.     p2->dosomething();  
  19.     p->dosomething();  
  20. }  

 자원 관리 객체는 하나의 자원만 받고 그 자원관리 객체를 여러개를 컨테이너로 관리 하면 자원이 여러개인 경우도 쉽게 관리를 할 수 있게 되는 것입니다.

 

 shared_ptr 삭제자  

 : shared_ptr은 특별하게 삭제자를 지정해 줄 수 있습니다. shared_ptr의 기본 삭제 동작은 delete를 호출하는 것인데, 이런 동작을 바꿔서

delete 배열 연산자를 호출 할수 있도록 바꿔 줄 수도 있습니다. 

view plaincopy to clipboardprint?

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4.   
  5. class Widget{  
  6. public:  
  7.     Widget(){cout<<" Widget() "<<endl;}  
  8.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  9.     void dosomething(){}  
  10. };  
  11.   
  12. struct arrayDeleter  
  13. {  
  14.     template<typename T>  
  15.     void operator()(T* p)  
  16.     {  
  17.         delete [] p;  
  18.     }  
  19. };  
  20. void main()  
  21. {  
  22.     std::tr1::shared_ptr<Widget> p(new Widget[4], arrayDeleter());  
  23. }  

 위와 같이 삭제자를 바꿀 수가 있습니다. 여기에서는 획득시 초기화를 이용해 자원을 넘기는데, 여기에 추가적인 인수를 더 받도록 되어 있습니다. 여기에 위에서 만든 arrayDeleter를 불러줘 삭제의 동작을 바꿔 주는 것입니다. 

 

 이렇게 삭제자 중에서는 우리는 가끔 빈삭제자를 이용해야 할 때가 있는데, 빈삭제자(Empty Deleter)는 삭제 동작을 아무런 동작을 취하지 않는 것을 말합니다.

view plaincopy to clipboardprint?

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4.   
  5. struct emptyDeleter  
  6. {  
  7.     template<typename T>  
  8.     void operator()(T* p)  
  9.     {  
  10.   
  11.     }  
  12. };  
  13.   
  14. class Widget{  
  15. public:  
  16.     Widget(){cout<<" Widget() "<<endl;}  
  17.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  18.     std::tr1::shared_ptr<Widget> getThis() {return std::tr1::shared_ptr<Widget>(this);}  
  19. };  
  20.   
  21. void main()  
  22. {  
  23.     std::tr1::shared_ptr<Widget> p(new Widget[4], emptyDeleter());  
  24. }  

 지금과 같은 상황에서 빈삭제자를 쓴다면 메모리 누수가 생길텐데, 이 빈삭제자의 활용법은 자기 자신의 객체를 외부로 반환하는 함수가 있다고 할때,  std::tr1::shared_ptr<Widget> getThis() {return std::tr1::shared_ptr<Widget>(this);}

 위와 같이 작성하면, this가 자원관리 객체로 넘어가면서 반환하게 되는데 shared_ptr의 기본 동작이 delete를 호출해주기 때문에, 지금같은 경우 this를 delete하면 문제가 생길것입니다. 그래서 여기서 빈삭제자를 넣어 이런 삭제를 막는 것입니다.

  std::tr1::shared_ptr<Widget> getThis() {return std::tr1::shared_ptr<Widget>(this, emptyDeleter() );}

 

 끝으로 shared_ptr 같이 타입이름이 너무 기니까 typedef를 쓰게 되는데 실제로 많이 쓰는 방법은 어떤 타입에 대한 shared_ptr 포인터 타입을 만들기 위해 위에서 전방 선언만 해주고 (컴파일러에게 이름을 알려준다.) 그 이름을 shared_ptr 포인터에 넘겨 새로운 타입을 만들어서 사용하면 이런 선언에 대한 이름길이의 부담을 줄일 수 있습니다.

view plaincopy to clipboardprint?

  1. #include <memory>  
  2. #include <iostream>  
  3. using namespace std;  
  4.   
  5. class Widget;  
  6. typedef std::tr1::shared_ptr<Widget> Widget_ptr;  
  7.   
  8. class Widget{  
  9. public:  
  10.     Widget(){cout<<" Widget() "<<endl;}  
  11.     ~Widget() {cout<<" ~Widget() " <<endl;}  
  12.     std::tr1::shared_ptr<Widget> getThis() {return std::tr1::shared_ptr<Widget>(this);}  
  13. };  
  14.   
  15. void main()  
  16. {  
  17.     Widget_ptr p(new Widget);  
  18. }  

 지금까지 자원관리에 대해 auto_ptr과 shared_ptr에 대해서 알아봤는데요, 사용자는 자원을 언제든지 놓칠 수 있기 때문에, delete를 언제나 챙길 수 없습니다. 그래서 이것을 자원관리 객체라는 또 다른 객체에 넘겨서 관리를 하자는게 이번 항목의 목적이라고 할 수 있습니다.

 

 

 * 자원 누출을 막기 위해, 생성자 안에서 자원을 획득하고 소멸자에서 그것을 해제하는 RAII 객체를 사용합시다.

 * 일반적으로 널리 쓰이는 RAII 클래스는 tr1::shared_ptr 그리고 auto_ptr입니다. 이 둘 가운데 tr1::shared_ptr이 복사 시의 동작이 직관적이기 때문에 대개 더 좋습니다. 반면, auto_ptr은 복사되는 객체(원본 객체)를 null로 만들어 버립니다.

 

 

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

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

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

 

 

 

출처: http://mastercho-textcube.blogspot.kr/2010/01/c-%EC%97%90%EC%84%9C-%ED%9E%99%EB%A9%94%EB%AA%A8%EB%A6%AC%EB%A5%BC-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B2%8C-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%91%EC%84%B1%ED%95%98%EC%9E%90.html

 

C++ 에서 힙메모리를 자동으로 관리하게 코드를 작성하자

 

 

아직도  C++은 동적메모리를  new delete로 관리 하여만 한다고 생각하는 사람이 많은것 같다

 

지금도 남이 짜 놓은 수 많은 코드속에서 new로 생성한 객체를  더미(raw)  포인터에 그냥 담고 ,  

"언제가 delete가 필요할때  완벽하게 delete 해줄수 있을거야" 라는 식의 코드를  보고 있는중이다

(오해는 없길 바란다 누굴 비난하기 위한 내용은 아니다)

 

이런 설계는 언제나 그렇듯 처음에는 무난한 ??? 설계가 될수 있으나

코드는 항상 수정되어야 하는 경우가 대부분이기때문에 수정할때마다

이 수많은 경우의 수를 다 파악해 delete 코드를 넣어주는 지옥과 같은 경험을 하게 된다

 

그래서 이러한 코드를 보게되면 ,코드 분석보단   " 아마 메모리 관리가 이렇게 힘들어서야... "

같은  반복된 생각만 든다

 

"이 중구 난방식의 new속에 , 도대체 delete를 어떻게 완벽히 기억해내어  처리해주지?"

 

 실용주의 프로그래머 책의 저자가  그러지 않았던가 ,  

'문제는  어떻게  완벽히 기억할수 있는게 아니라, 언제 잊어버릴것인가' 이다 [물론 정확한 인용은 아니다]

 

알고리즘의 핵심이 아닌상  , 잘잘하고 구차한 문법문제는  

잊어버려도 문제가 없는 코드가 구성되어야 좋은 코드라 할수 있는것이다.

 

자바가 C++ 프로그래머를 대량으로 흡수할때 1순위로 강조한것이 무엇인가?

가비지 컬랙션이 아니었던가?

쓸때없는 것에 머리 쓰지 않아도 되는건 대단한 강점인것이다

 

소실적에는 c++에 가비지 콜랙션을 만들수 있지 않을까하는  생각도 해보았으나

이런 당연하고도 고전적인 문제를 C++이 해결을 못했을리는 없다

 

믿을지 모르겠지만  나는 약 6년전 대학교때 STL과 BOOST를 공부한 이후 단한번도 직접 delete를 작성한적이

없다. 혹!? 모르나 아마 없을것이다

[물론 crt 디버그에서 메모리릭이라 투털거린적 역시 없다]

 

믿을수 없다고 ??

 

지금 말한 3가지만 사용한다면  앞으로 99.9%  delete를 직접 할 필요가 없다

1. 스트링

2. 자료구조

3. 스마트 포인터

 

 

1.  스트링

c언어 스트링은 가장 고전적이고 다루기 어려운 구조이다

스트링 특성상 가변적으로 크기가 변하는게 대부분이기때문에

동적 메모리를 활용하는 경우가 많고 , 경우에 따라서 관리가 복잡하다

 

하지만 스트링 객체를 쓰면 대부분 해소 되는데

표준 std::string이 아니더라도  스트링 객체를 활용한다면 구질 구질한 c언어 스트링으로 인한

new char[XX]같은  동적 메모리 사용을 피할수 있다

 

아직도 문자를 생성할때  new char[XX] 쓰고 있다면 반드시 표준std::string 를 이용해보자

다른 세상을 경험할수 있을것이다

 

그런데도 굳이 스트링 객체 자체를 new로 생성하는 몰지각스러운 코드를 만나면 답이 안나온다

스트링 객체를 대부분  그렇게 생성되어야 할 이유가 99%이상  없기때문이다

 

2. 자료구조

C++의 대표적인 자료구조로는 STL이 존재한다

STL이 객체를 다루는 방법이   복사를 이용하기때문에 주로 가벼운 객체를 주로 담게되나

어지간히 무겁지 않는한  복사가  new 로 할당해 담는 비용보다  훨씬 싼편에 속한다

만약 정말로 무겁거나 객체의 다형성을 위해 new를 이용해야 한다면 스마트포인터를 담으면 그만이다

 스캇마이어(Effective STL)가 그리 강조하지 않던가?

 

컴퓨터 공학생이라면 필수로 읽어봤을 자료구조책에   당골로 등장하는

vector list hash map deque 등 [이름은 조금 다를수도 있다] 을

STL를 이용하면 공짜로 이용할수 있으며

이것은 곧  가면적으로 크기가 변하는 컨테이너를 공짜로 사용할수 있다는 말이다

 

new를 이용하는 가장 핵심적인 이유중 한가지가 가변 크기때문 아니던가? 그것을 자동으로 STL

컨테이너가 해주기때문에 new를 쓸이 없어진다

[물론 hash나 set, map은 그뿐이 아니라 , 키로 데이터 관리까지 해주니까 두말할 나위가 없다]

 

그중 new를 이용한 배열의 경우

 

 vector를 이용하면 C의 배열과 100% 호환되기때문에 그대로 C API와 더불어 운영될수 있으며

컴파일러의 최적화 능력 [STL의 성능은 다음에 다루기로 하겠다]

까지 더해지면 vector의 편리함과 C배열과 같은 성능을  동시에 누릴수도 있는것이다

게다가 수백만명이 검증한 STL 자료구조의 안정성도 더불어 누릴것이다

 

3. 스마트 포인터

new 직접 사용에 대해 계속해서 부정적인 이야기를 했지만

반드시 직접 힙메모리를 이용해야 하는 경우가 존재한다

다형성이 존재하는 객체를 컨테이너에 담을때 부모형태로 포인터를 저장해 관리 할때는

대부분 new를 피하기 어렵다

그래서 생겨난것이 스마트 포인터이다 

[ 물론 객체 크기가 커서 new로 생성해 스마트 포인터에 담는 경우는 예외이다 ]

 

스마트 포인터의 기본 이념은  new생성된 포인터를  객체가

참조 카운팅(std::auto_ptr 같은것은 제외) 관리해서 유지하는것이다

 

참조하는 객체가 전부 사라지면 그때에 delete 된다

 

가장 대표적인게 표준으로도 포함된 boost::shared_ptr이 될것이다 [boost.org의 doc를 참조하라]

 

기본적으로 가비지 컬랙션을 사용하는 언어들도 내부적으로는 참조 카운팅을 유지하고 있다

[원리는 같다는 것! ,다만 C++은 참조하는 객체가 사라질때 바로 제거 된고 , 다른 언어들은

언어의 가비지 컬랙션 정책에 따라 다를것이다 ]

 

 

물론 만능은 아니다

스마트 포인터를 이용할때   내부의 스마트포인가 서로를 포함하는 구조로 객체가 구성될 경우

참조 카운팅이 꼬여 메모리가 해제가 안될수 있는것이다

 

그것을 위해 weaken 어쩌구 방식의 스마트 포인터도 boost에서 지원하는데

그러한 꼬임은

그것은 C++의 스마트 포인터가 아니라 가비질 컬랙션을 지원하는

어떤 언어에서도  정상적인 방법으로 메모리를 해제할수가 없다

 

다른 언어에서도 boost의  weaken 어쩌고 같은

방식을 이용해야 한다 , 한마디로  상호 포함관계의 스마트 포인터는  정상인 논리 구조라 보기 힘들다고 생각한다

 

그것을 이용할 정도면  설계 자체가 이해하기 어려운 구조기때문에

설계를 바꾸는게 정답일것이다

 

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

참고로

 

boost의 shared_ptr은  delete 뿐 아니라 직접 제거자를 지정해 주어

delete 대신  다른 해제 방법을 이용할수 있게 해놓았다

new로 생성하지 않고 create나 destroy를 이용해야 하는 리소스도

대응해서 사용할수 있는것이다

 

혹 STL에 스마트 포인터를 담으면  참조할때  이터레이터 참조뿐 아니라 스마트 포인터를 참조해야하기에 **iterator를 쓰게되어 가독성이 떨어지 거슬리는분들이 존재할거라 생각된다

나 역시 그랬으니까

 

그건 boost의     boost/ptr_container/ptr_list.hpp 같은걸 이용하면 된다

부스트의 ptr_containter에는  new로 생성된 포인터를 직접 넣어주면 

컨테이너에서 제거 될때 자동으로 delete를 해준다

 

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

 

 

STL 스마트 포인터 알게된지 이후 6년동안 delete 코드를 작성해본적이 없지만

힙메모리를 이용하고도  , delete 코드가 없다하면 아직도 믿지 못하는 사람이 많다

 

gpg study에서도 비슷한 발언을 했을적에  믿기 힘들다는 사람을 대다수인걸보면

얼마나 많은 사람들이  아직도  힙메모리를 직접 다루고 있는지 느껴지는듯했다

 

 

프로그래머라면 정말 중요한 알고리즘을 기억하는데만해도 머리에 담을 용량이 부족하지

않았던가?

 

 

다음 편엔 STL 자료구조에 대해 이야기 해보겠다

덤으로 fix_vector도 ~ ^^

 

 

 

 

작성자: mastercho 시간: 오후 12:19 

이메일로 전송BlogThis!Twitter에서 공유Facebook에서 공유Pinterest에 공유

 

 

 

댓글 4개:

  1. neolith2010년 1월 7일 오전 3:28답글
  2. 레퍼런스 카운팅 기반의 가비지 컬렉터는 순환참조에 취약하지만, mark and sweep 방식의 가비지 컬렉터는 순환참조된 오브젝트들을 다루는데 문제가 없지 않나요?



    http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)
  3. mastercho2010년 1월 7일 오전 10:48답글
  4. mark and sweep 이것을 간과 했었네요



    대부분 가비지 컬랙션이 시간이 많이 걸리는 작업이므로

    mark and sweep보단 참조 카운팅을 주로 사용한다고 가정해 버렸습니다



    아무래도 mark and sweep는 재귀적으로 다 체크를 해봐야 하는것이니까요

    물론 이것도 나눠서 처리할순 있겠습니다만...
  5. leaf2010년 1월 11일 오후 5:35답글
  6. 니가 알려줘서 첨에는 사용하는데 꽤 불편했는데.. 무작정 믿고 개발했는데 특별히 문제가 안생겨서 좋았다. 앞으로 더 좋은글 많이 올려줘~

    ㅅㄱ
  7. mastercho2010년 1월 11일 오후 6:23
  8. @leaf - 2010/01/11 17:35
    야 답글이 초큼 이상하군 , 5년전 내가 STL과 스마트 포인터를 전수해줬을 적에 장족의 발전을 했던넘의 말투치고 좀 이상하군



    "특별히 문제가 안생겨서 좋았다" <- 오해의 여지가 있는 말투넹 -_-;



    모범 답안을 꼭 내가 알려줘야돼? 

    정답 --- "참 좋았던거 같아" ---- 

     

 

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

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

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

 

 

출처: http://itability.tistory.com/37

 

new를 바로 쓰기보다는 std::make_unique나 std::make_shared를 쓰자

매일매일 modern c++ 2015.04.16 01:38

std::make_shared는 C++11입니다.

 

std::make_unique는 C++14입니다.

 

만약 C++11을 사용하고 있다고 하더라도 다음과 같이 std::make_unique

 

를 만들 수 있습니다.

 

template<typename T, typename... Ts> std::unique_ptr<T> make_unique(Ts&&... params) { return std::unique_ptr<T>(new T(std::forward<Ts>(params)...)); }

 

 

이 예는 약간의 노력으로 사용자가 필요하다면 make_unique를 만들어

 

낼 수 있다는 것을 보여줍니다. 단지 기억해야 하는 것은namespace std에

 

사용자가 정의한 make_unique를 포함시키기 않는 것입니다. 이는 나중

 

에 C++14로 업그레이드 했을 때의 충돌을 방지하기 위해서 입니다. make함수

 

는 총 3개가 있습니다. make 함수는 임의의 인자를 갖습니다. 그리고 동적으로

 

할당되는 객체의 생성자에 해당 인자들을 perfect-forward하게 전달 합니다. 그

 

리고 그 객체에 대한스마트 포인터를 반환합니다. 이러한 특성을 가지고 있는

 

make 함수중 std::make_shared와 std::make_unique를 살펴보겠습니다.

 

세번째 make 함수는 std::allocate_shared 입니다.

 

이 make 함수는 std::make_shared처럼 작동 하는데 함수의 첫 번째 매개변수

 

가 동적으로 할당 될 객체의 Allocator가 된다는 점만 다릅니다.

 

 

make 함수를 사용해서 스마트 포인터를 만드는 것이

 

더 좋은 이유

 

auto upw1(std::make_unique<Widget>()); // make 함수 사용 std::unique_ptr<Widget> upw2(new Widget); // make 함수 비사용 auto spw1(std::make_shared<Widget>()); // make 함수 사용 std::shared_ptr<Widget> spw2(new Widget); // make 함수 비사용

 

 make 함수를 사용하는 첫 번째 이점은 make 함수가 new를 사용하지 않

 

는다는 점입니다. 

 

 make 함수를 사용하지 않을 때  Widget이라는 type이 2번 등장하고 있습

 

니다. 이는 중복되는 코드를 피하자는 소프트웨어 엔지니어링의 중요 견해

 

와 충돌하게 되는 지점입니다. 중복 코드가 나타났을 때 생기는 컴파일 타임

 

의 증가, 코드량의 증가, 일치하지 않는 코드가 나타나 생길 수 있는 버그등

 

의 문제 때문에 make 함수는 선호 됩니다. 

 

 두 번째 차이는 make 함수가 exception safety 하게 작업이 이루어진다는

 

것입니다.

 

 다음의 예를 통해서 위 내용을 살펴보도록 하겠습니다. 

 

우선순위를 가지고 Widget 객체를 처리하는 함수가 입니다. 

 

void processWidget(std::shared_ptr<Widget> spw, int priority);

 

값으로 전달되는 shared_ptr이 의심스럽지만 Item41에서 processWidget이

 

항상 std::shared_ptr의 복사본을 만들어야 한다면 이 함수는 타당하게 만들

 

어졌다고 할 수 있습니다. 

 

 여기서 우선순위를 계산하는 함수를 다음과 같다고 하겠습니다. 

 

int computePriority();

 

이제 make 함수를 상용하지 않고 processWidget 함수를 실행 하겠습니다.

 

processWidget(std::shared_ptr<Widget>(new Widget), computePriority());

 

 

이 함수를 실행하기 위해서는 다음의 세 가지가 먼저 실행되야 합니다. 

 

1. 새로운 Widget 객체 생성 ( new를 통해서)

 

2. Widget 객체를 인자로 전달해서 std::shared_ptr 생성

 

3. computePriority 함수 실행

 

이 세 가지의 실행이 컴파일러마다 다른 순서로 일어나 문제가 발생할 수 있

 

습니다. 만약 1-3-2 순서로 실행이 이루어지는 경우 1번에서 Widget 객체가

 

생성되고 3번에서 computePriority 함수에서 예외가 발생할 수 있습니다. 

 

이렇게 되면 Widget 객체는 만들어진 후 std::shared_ptr에 의해서 관리될

 

수 없으므로 자원 누출이 일어납니다. 

 

std::make_shared<Widget>()은 위와 같은 문제를 피할 수 있습니다. 

 

make_shared를 사용한 예제는 다음과 같습니다. 

 

processWidget(std::make_shared<Widget>(), computePriority());

 

 std::make_shared 함수가 Widget 포인터를 가지고 있는 shared_ptr을

 

반환하므로 위에서 1-2번이 한꺼번에 처리됩니다. 따라서 처리 순서로 인한

 

문제가 발생하지 않습니다.

 

 

 

std::make_shared는 컴파일러에 의해 더 작고 빠른 코드로 만들어집니다.

 

이 때 더 날씬한 구조체를 사용하기 때문입니다.

 

new를 사용하는 다음과 같은 코드가 있습니다.

std::shared_ptr<Widget> spw(new Widget);

 

여기서는 총  2번의 메모리 할당이 일어납니다. 눈에 보이는 new로 인한

 

Widget의 할당과 눈에 보이지 않는 control Block의 할당이 그것입니다.

 

std::make_shared를 사용하는 예를 보겠습니다.

 

auto spw = std::make_shared<Widget>();

 

여기서는 1번의 메모리 할당이 일어납니다. std::make_shared를 사용하면

 

Widget에 대한 할당과 control Block의 할당을 한 번에 처리합니다.

 

따라서 실행 코드의 속도를 높입니다.

 

 

 

make 함수를 사용하지 말아야 할 때

 

1. 삭제자를 지정하려고 할 때

 

 make 함수는 삭제자를 지정할 방법이 없습니다.  

 

2. std::initializer_list를 매개변수로 갖는 생성자가 오버로드 되어 있는 클래스

 

auto upv = std::make_unique<std::vector<int>>(10, 20); auto spv = std::make_shared<std::vector<int>>(10, 20); // std::make_shared<std::vector<int>>{10,20}; 은 애당초 컴파일이 안됨 // make_shared는 중괄호를 지원하지 않음

 

위의 예에서 upv와 spv는 값이 20인 원소 10개짜리 벡터를 갖는 스마트 포인

 

터가 된다. 따라서 중괄호 초기화한 값을 가지고 작업을 하고 싶다면 new를 사

 

용해야 합니다.  make함수를 사용할 수 없는 것입니다.

 

하지만 auto 타입을 사용하면 이를 극복할 수 있습니다.

auto initList = { 10, 20 }; // auto를 통해 std::initializer_list 생성자를 사용하기 // make 함수와 중괄호 초기화를 같이 사용할 수 있게 되었다 auto spv = std::make_shared<std::vector<int>>(initList);

 

3. std::make_shared를 사용하지 말아야 할 때

 

이 부분은 std::make_shared만 연관이 있습니다.

 

사용자가 어떤 클래스에서 new와 delete를 새롭게 정의 했을 때

 

사용자는 종종 new가 정확히 클래스 사이즈 만큼의 크기만큼만 메모리 할당을

 

하게 합니다. 이는 만약 Widget 클래스에 대한 new를 새롭게 정의했을 때

 

`sizeof(Widget)만큼의 크기`만 new를 통해 할당되는 상황을 말합니다.

 

이 때 std::make_shared 함수를 사용하면 문제가 발생할 수 있습니다.

 

왜냐면 실제로 control blcok의 크기만큼의 메모리 공간이 더 필요하기 때문입

 

니다. std::make_shared 함수 내부에서는 전달된 타입의 객체으로 만들 수 있

 

는 객체와 control block을 동시에 메모리 할당 합니다. 따라서 custom 메모리

 

할당자는 이 작업에 영향을 미칠 수 있습니다. 원하는 크기보다 control block

 

만큼 크기가 빠진 채로 할당 될 수 있습니다.  

 

 

 

4. std::make_shared가 가리키는 오브젝트의 메모리 해제 시기

 

shared_ptr와 관련된 정보를 담는 control block에 대해서 이전 Item에서 다루었

 

습니다. control block에는 몇 개의 std::shared_ptr이 해당 객체를 가리키는지

 

알려주는 참조 카운트와 몇 개의 std::weak_ptr이 해당 객체를 가리키는지 알려

 

주는 참조 카운트에 대한 정보가 있습니다. 전자를 참조 카운트 후자를 weak

 

카운트라 하겠습니다. 참조 카운트가 0 이 되면 연관되어 있는 weak_ptr의

 

expired 함수를 통해서 확인할 수 있습니다.

 

여기까지 앞 item에서 다룬 내용이었습니다.  이번에 이야기 할 것은 make 함수

 

를 통해 가리키고 있는 객체와 shared_ptr을 통해 가리키고 있는 객체의 메모리

 

해제 시기가 다르다는 점입니다.

 

std::shared_ptr에 new로 만들어진 객체를 참조할 때 control block을 만듭니다.

 

그러면 new로 만들어진 객체에 할당된 메모리와 control block에 할당된

 

메모리는 따로 관리 됩니다. std::shared_ptr의 참조 카운트가 0이 될 때

 

new로 만들어진 객체는 소멸자가 불리고 -> 메모리가 해제 됩니다. 후에

 

weak_ptr이 0이 되면 control block에 대한 메모리도 해제 됩니다.

 

하지만

 

std::make_ptr<T>는 내부에서 new T를 하고 control block과 메모리를 함께

 

관리 합니다. 즉 참조 카운트가 0이 되어도 T에 대한 메모리가 해제 되지 않

 

습니다. weak 카운트가 0이 되면 control block의 메모리와 T에 대한 메모리가

 

함께 해제 됩니다.

 

이를 다음의 예제를 통해 다시 알아보겠습니다.

// 매우 큰 타입, 메모리를 많이 차지한다고 하자 class ReallyBigType { … }; // new를 통해 객체를 할당하고 이를 가리키는 스마트 포인터 생성 std::shared_ptr<ReallyBigType> pBigObj(new ReallyBigType); ... // std::shared_ptr을 여러 개 사용, std::weak_ptr을 여러 개 사용 ... // 마지막 std::shared_ptr이 파괴됨, 아직 std::weak_ptr을 남아 있음 // 이 시점에 new로 할당한 객체는 소멸자가 불리고 메모리 해제 ... // 이 기간동안 control block만 할당된 상태 ... // 마지막 std::weak_ptr도 더 이상 객체를 가리키지 않으면 control block 메모리 해제 //

// std::make_shared 함수 통해 스마트 포인터 만듦 // 내부적으로 타입 인자로 넘어온 객체를 할당함 auto pBigObj = std::make_shared<ReallyBigType>(); ... // std::shared_ptr을 여러 개 사용, std::weak_ptr을 여러 개 사용 ... // 마지막 std::shared_ptr이 파괴됨, 아직 std::weak_ptr을 남아 있음 // 할당된 객체의 소멸자 호출 하지만 메모리는 해제하지 않음 // 이 시점에 아무것도 메모리 해제 되지 않음 ... // 이 기간동안 참조하고 있는 std::shared_ptr이 없는데도 // 매우 큰 메모리가 할당되어 있음 ... // 마지막 std::weak_ptr이 파괴되면 control block과 할당된 객체 동시에 메모리 해제

 

이렇게 std::make_shared의 단점 때문에 std::make_shraed를 사용할 수 없는

 

상황이라면 Scott Meyers가 추천하는 스마트 포인터 사용방식은 아래와 같습

 

니다.

 

// shared_ptr이 먼저 객체를 가리키고 있게 한다 // new와 함수를 한 인자로 넘기면서 생기는 예외상황을 피할 수 있다 std::shared_ptr<Widget> spw(new Widget, cusDel); processWidget(std::move(spw), computePriority()); // std::move를 통해서 스마트 포인터를 전달한다 // std::move가 아닌 값에 의한 전달이었다면 // 참조 카운트를 증가시킨다. // 하지만 std::move는 참조카운트를 증가시키지 않고 // 복사하는데 드는 비용도 생기지 않는다.

 

 

기억할 점

 

make 함수는 코드의 중복을 막고, 예외에도 안전하며, 빠릅니다

 

make 함수가 어울리지 않는 상황도 있습니다

 

std::make_shared와 std::shared_ptr의 메모리 관리 정책을 알아야 합니다

 

 

 

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

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

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

 

 

출처ㅣ http://sonchulmin.blogspot.kr/2016/07/c-stl-3.html

C++ STL 수업 - 3일차

 


스마트 포인터

#include "show.h"
#include <memory>
/*
스마트 포인터
C/C++에서는 new는 한곳이지만, delete는 여러 곳에 존재.
아이디어 : 포인터는 블럭 벗어나도 사라지지 않지만,
           [객체]는 블럭 벗어날 때 자동 delete된다.

실제는 [객체]이지만, *, ++, --, -> 등 override해서 지원. 마치 포인터 처럼 사용.
3가지 : shared_ptr, weak_ptr, unique_ptr
*/

class Car
{
public:
    void Go() { cout << "Car Go" << endl; }
    ~Car()    { cout << "Car 파괴" << endl; }
};

#if 0
int main()
{
    Car *p = new Car;
    delete p;
}
#endif


int main()
{
    //    shared_ptr<Car> p = new Car;  // ERROR. explicit 생성자는 =로 할당 불가능
    shared_ptr<Car> p(new Car);   // OK. 생성자가 explicit 생성자 이기 때문이다.
                                  // shared_ptr<Car>는 객체다. 포인터는 아니다.
    p->Go(); // 그럼에도 p에 ->를 붙여 포인터처럼 쓰는 것은 -> operator를 재정의 한것이다.
             // 스콥이 나가면 Car는 destroy 된다. 때문에 delete 불필요. 게임 업체에서는 진짜 포인터 안씀. 
    return 0;
}



#include "show.h"
#include <memory>
/*
스마트 포인터
C/C++에서는 new는 한곳이지만, delete는 여러 곳에 존재.
아이디어 : 포인터는 블럭 벗어나도 사라지지 않지만,
[객체]는 블럭 벗어날 때 자동 delete된다.

*/

class Car
{
public:
    void Go() { cout << "Car Go" << endl; }
    ~Car() { cout << "Car 파괴" << endl; }
};

/* p1 p2도 둘다 같은 object를 가리키게 됨.
참조 개수를 관리 한다. 모든 스마트 포인터가 같은 참조 개수를 공유 한다.

1
2
1
A
Car 파괴
*/
int main()
{
    shared_ptr<Car> p1(new Car);

    cout << p1.use_count() << endl;
    {
        shared_ptr<Car> p2 = p1;
        cout << p1.use_count() << endl;
    }
    cout << p1.use_count() << endl;

    cout << "A" << endl;
    return 0;

} // ~Car() 불림.




#include "show.h"
#include <memory>
/*
스마트 포인터
C/C++에서는 new는 한곳이지만, delete는 여러 곳에 존재.
아이디어 : 포인터는 블럭 벗어나도 사라지지 않지만,
[객체]는 블럭 벗어날 때 자동 delete된다.

*/

class Car
{
    Car(int c){}
    int color;
public:
    void Go() { cout << "Car Go" << endl; }
    ~Car() { cout << "Car 파괴" << endl; }
};

void* operator new(size_t sz)
{
    cout << "new : " << sz << endl;
    return malloc(sz);
}

void operator delete(void*p)
{
    free(p);
}

/* 

int main()
{
    // 아래 코드가 실행되면 메모리 할당이 몇번 일어 날까요 ?
    shared_ptr<Car> p1(new Car);
    
    1. HEAP  : new Car에 의해서 한번 발생 (new 1회)
    2. STACK : p1은 stack에 생김.
    3. HEAP  : [참조 개수] + 알파를 관리하기 위한 관리 객체가 따로 만들어짐. (new 1회)
    > 1과 3 각각 1회씩 2회의 new가 발생함.
    > 3때매 파편화 문제 발생.
    > 대응 방법으로, 1에 붙여서 3을 할당 받는 방법 생각 가능.
    

}
*/

int main()
{
    shared_ptr<Car> p1(new Car);
    // new : 4  원본객체
    // new : 16 참조개수관리객체
    
    // sizeof(Car) + sizeof(참조개수관리객체) 크기를 한번에 메모리 할당.
    // shared_ptr 쓴다면 반드시 make_shared 써야 한다 !
    shared_ptr<Car> p2 = make_shared<Car>(10);
    // 이번에는 new가 전체 1회만 발생 함.
    // new : 16 원본객체+참조개수관리객체
    
    /*
    make_shared<>로 (1) 원본객체(4바이트)와 (2) 참조개수관리객체(16바이트)를 합쳤음에도 20바이트가 아닌 16바이트인 것은
    원래 참조개수관리객체가 별도로 존재할 때 크기 16바이트 중 4바이트는 원본객체에 대한 포인터임.
    그런데 참조개수관리객체가 원본객체 끝에 달라붙으면서 그 4바이트가 필요 없게 됨.
    그래서 참조개수관리의 크기는 12바이트가 되고, 원본객체의 크기(int color 4바이트)를 더해서 16바이트가 됨.
    */
}




#include "show.h"
#include <memory>
#include <type_traits> //remove_pointer

#include <windows.h>
void foo(int *p)
{
    cout << "foo" << endl;
    delete[] p;
}

struct ArrayDeleter
{
    void operator()(int *p) { delete[] p; }
};

int main()
{
    //shared_ptr<포인터 뺀타입> p(포인터 타입);
    // 삭제자의 전달.
    shared_ptr<int> p1(new int); // new는 delete로 삭제
    shared_ptr<int> p2(new int[10], foo); // new[]는 delete[]로 삭제해야 함. 때문에 삭제자를 전달해서 삭제 가능.
    shared_ptr<int> p3(new int[10], ArrayDeleter()); // 임시객체로 전달
    shared_ptr<int> p4(new int[10], [](int *p) {delete[] p;}); // 람다로 전달 C++11

    shared_ptr<FILE> p5(fopen("a.txt", "wt"), fclose); // 삭제자를 fclose로 제공. 인자가 1개 이상이면, bind를 사용할 수 있다.

    //shared_ptr<포인터 뺀타입> p(포인터 타입); 이어야 하는데,
    // 아래와 같이 HANDLE도 포인터 타입일 때 처리 방법.
    HANDLE h = CreateEvent(0, 0, 0, 0);
    CloseHandle(h);
    shared_ptr<remove_pointer<HANDLE>::type> h2(CreateEvent(0, 0, 0, 0), CloseHandle);

    // 현재는 삭제자를 쓸 때는 make_shared<>를 못 씀 !
    // make_shared<> 사용시 삭제자 변경 불가능. 이후 표준에서 논의 중. 표준 위원회에 요구 사항 많음.
#if 0
    int *p1 = new int;
    delete p1;

    int *p2 = new int[10];
    delete p2; 
    // C++ 표준에는 new -> delete, new [] -> delete[]만 써 있음. 그외에는 정의 안되어 있음. 그것은 컴파일러 따라 다름. "undefined" 항목.
    // 때문에 new [] -> delete는 쓰면 안됨.
#endif    

}



#include "show.h"
#include <memory>

// 중요 예제...
// 참조계수 스마트 포인터가 상호 참조가 발생하면 메모리 누수 발생 함.
struct Node {
    int data;
    // Node* next; 
    // 스마트 포인터를 쓰기로 했으면, 일반 포인터와 스마트를 섞는것보다, 모두 스마트로 하는게 좋다.
    shared_ptr<Node> next;

    ~Node() { cout << "Node파괴" << endl; }
};

/*
  스마트 포인터를 쓰기로 했으면, 일반 포인터와 스마트를 섞는것보다, 모두 스마트로 하는게 좋다.

*/
#if 0
int main()
{
    shared_ptr<Node> p1(new Node);
    shared_ptr<Node> p2(new Node);

    // 스마트 포인터를 써도, 아래 경우 Node가 파괴가 안됨. 
    // [상호 참조]가 일어나면 메모리 해지가 안되어 [메모리 누수] 발생 함.
    p1->next = p2;
    p2->next = p1;
}
#endif


struct Node2 {
    int data;
    // Node* next; 
    // 스마트 포인터를 쓰기로 했으면, 일반 포인터와 스마트를 섞는것보다, 모두 스마트로 하는게 좋다.
    weak_ptr<Node2> next;

    ~Node2() { cout << "Node2파괴" << endl; }
};

// 해결책으로, 참조개수가 증가되지 않는 스마트 포인터가 필요. weak_ptr<>
// s로 시작 하는 스마트 포인터는 모두 참조 계수 개체
// w로 시작 하는 스마트 포인터는 weak 참조 
int main()
{
    shared_ptr<Node2> p1(new Node2);
    shared_ptr<Node2> p2(new Node2);

    p1->next = p2;
    p2->next = p1;
}


일반 포인터가 아닌 weak_ptr<>을 써야 하는 이유 
#include "show.h"
#include <memory>

int main()
{
    int *p = 0;

    weak_ptr<int> wp;
    {
        shared_ptr<int> sp(new int);

        cout << sp.use_count() << endl; // 1
        
        p = sp.get();
        wp = sp;

        cout << sp.use_count() << endl; // 1
    } // shared_ptr<int> sp(new int); 자원 파괴 됨.

    cout << p << endl; 
    // 이 경우 [자원]은 파괴 되었지만, 일반포인터 p가 [주소]는 계속 가지고 있음.
    // p는 [자원]이 진짜 살아 있는지 아닌지 알 수 있는 방법이 없음.

    // 해결책:
    // wp는 자원뿐만 아니라, 참조개수관리객체도 가리키게 됨.
    // wp가 하나라도 살아 있으면 [자원 객체]는 파괴되더라도 [참조개수관리객체]는 살아 있게 됨.
    // weak_ptr<>를 사용시 자원이 파괴 되었는지 알 수 있다.

    cout << wp.use_count() << endl; // 0 : [자원]이 파괴 되었음을 알 수 있음.
    // make_shared여도 역시 가능.

    // 진짜 중요한 것은 아래와 같이 쓸 수 있느냐? 절대 안됨. 멀티 쓰레드 환경에서는 문제가 됨.
#if 0
    if (wp.use_count() > 0) { // 여기가 true이다가 다른 쓰레드에 의해서 파괴되는 경우.
        // 자원이 살아 있다고 판단하고 사용 하는 경우
        wp->멤버 호출(); // 이 시점에는 false 일 수 있다.
    }
#endif
    // 때문에 wp는 ->연산자를 지원하지 않는다.
    // 즉, 자원 접근을 허용하지 않는다 !!!

    // weak_ptr<>로 shared_ptr<>을 다시 [생성]해야 합니다 !!
    shared_ptr<int> sp2 = wp.lock(); 
    // 여기 실행되기 전에 죽으면 상관 없으나, [자원]이 있었으면 다른 thread에 의해서 파괴될 수 없다.
    // 기 자원이 파괴된 경우는 sp2는 0;
    // lock이 참조개수의 thread safe을 보장 한다. wp.lock()은 shared_ptr<>을 하나 생성해서 넘겨 준다.
    
    // Android는 wp.promote();
    if (sp2 == 0)
        cout << "자원 파괴 됨" << endl;
    else
        cout << "자원 사용 가능" << endl;

}


쓰레드와 스마트 포인터가 섞여 있을 때 문제 점
#include "show.h"
#include <memory>
#include <windows.h>

// cafe.naver.com/cppmaster 예전 수업 자료실에서 "키캣" 검색해서 받아 보기
// core/core/libutils/thread.cpp

// 3번째 파라미터를 thread로 실행 함.
class Thread
{
public:
    void run() { CreateThread(0, 0, foo, this, 0, 0); }
    static DWORD __stdcall foo(void*p) {
        Thread* self = (Thread*)p;
        self->threadMain();
        return 0;
    }

    virtual void threadMain(){}
};

class MyThread : public Thread 
{
    int data;
public:
    virtual void threadMain() { data = 10; cout << "my thread" << endl; }
};

int main()
{
    {
        shared_ptr<MyThread> p(new MyThread);
        p->run();
    }
    // 이 블럭을 벗어나는 순가 p는 destory 됨. 반면 thread는 동작 중.
    // 쓰레드 객체의 수명을 쓰레드가 죽을 때까지 이어야 한다.
    // 그 방법?

    while (1);
}


#include "show.h"
#include <memory>
#include <windows.h>

// 쓰레드와 스마트 포인터가 섞여 있을 때 문제 점

// cafe.naver.com/cppmaster 예전 수업 자료실에서 "키캣" 검색해서 받아 보기
// core/core/libutils/thread.cpp

// 그럼 언제 shared_ptr<>을 증가시켜줄 것인가가 문제.
class Thread
{
    shared_ptr<Thread> mHoldSelf;// 자신의 참조 계수를 증가하기 위해.

public:
//    Thread() : mHoldSelf(this) {} // 방1. 생성자에서 해도 되는가 ? 안됨. thread 안만들면 메모리 leak이 발생. 때문에 thread 만들 때 해야 함.

    void run(shared_ptr<Thread> sp) { 
        //mHoldSelf = this; // 방2. 이렇게 해도 될까 ?
        mHoldSelf = sp; // 방3.
        cout << ">"<<mHoldSelf.use_count() << endl;
        CreateThread(0, 0, foo, this, 0, 0); 
    }
    static DWORD __stdcall foo(void*p) {
        Thread* self = (Thread*)p;
        self->threadMain();

        // 참조 계수를 줄여 준다. mHoldSelf은 더이상 [자원] 참조하지 않는다. 참조계수를 무조건 0으로 만드는 것은 아님
        self->mHoldSelf.reset();
        return 0;
    }

    virtual void threadMain() {}
};

class MyThread : public Thread
{
    int data;
public:
    virtual void threadMain() { data = 10; cout << "my thread" << endl; }
    ~MyThread() { cout << "~MyThread" << endl; }
};

int main()
{
    {
        shared_ptr<MyThread> p(new MyThread);
        p->run(p);
    }
    // 이 블럭을 벗어나는 순가 p는 destory 됨. 반면 thread는 동작 중.
    // 쓰레드 객체의 수명을 쓰레드가 죽을 때까지 이어야 한다.
    // 그 방법?

    while (1);
}

/*
방2 안되는 이유.
shared_ptr<int> p1(new int);
shared_ptr<int> p2 = p1; // 참조 계수 2

int *p = new int;
shared_ptr<int> p3(p); // 계수 1
shared_ptr<int> p4(p); // 계수 1
*/



#include "show.h"
#include <memory>
#include <windows.h>
// 쓰레드와 스마트 포인터가 섞여 있을 때 문제 점

// 외부에서 shared_ptr<>로 [객체]를 관리할 때
// [객체] 스스로가 자신의 참조개수를 증가하고 싶다면
// enable_shared_from_this<> 사용 한다.

class Thread : public enable_shared_from_this<Thread> //this 로 부터 shared_ptr의 참조 개수를 증가하게 해달라
{
    shared_ptr<Thread> mHoldSelf;// 자신의 참조 계수를 증가하기 위해.

public:

    void run() {
        mHoldSelf = shared_from_this(); // 외부에서 만든 shared_ptr<>이 사용하는 관리 객체를 공유하게 한다.
        cout << ">" << mHoldSelf.use_count() << endl;
        CreateThread(0, 0, foo, this, 0, 0);
    }

    static DWORD __stdcall foo(void*p) {
        Thread* self = (Thread*)p;

        // 방2. mHoldSelf를 [지역변수]에 옮겨 담는다. reset 이후에 실수로 코드 불러서 문제 생기는 것 방지 할 수 있음 !
        shared_ptr<Thread> me(self->mHoldSelf);
        self->mHoldSelf.reset();

        self->threadMain();
        // 방1. self->mHoldSelf.reset(); 이것 대신 
          // 방1 사용시 cout << data << end; // 문제 발생
        return 0;
    }

    virtual void threadMain() {}
};

class MyThread : public Thread
{
    int data;
public:
    virtual void threadMain() { data = 10; cout << "my thread" << endl; }
    ~MyThread() { cout << "~MyThread" << endl; }
};

int main()
{
    {
        shared_ptr<MyThread> p(new MyThread);
        p->run();
    }
    while (1);
}



#include "show.h"
#include <memory>

/*
관리 객체
*/
int main()
{
    shared_ptr<int> p1(new int); // 자원 + 관리객체 생성
    shared_ptr<int> p2 = p1;


    unique_ptr<int> up1(new int); // 자원만 생성. 관리 객체가 없다. 소멸자에서 delete 기능만 제공.
    //unique_ptr<int> up2 = up1; // unique_ptr<>은 복사 생성자를 지워버림. compile error 발생.

    unique_ptr<int> up2 = move(up1); // move은 원본 reset. unique_ptr은 복사는 안되지만 move는 가능 함.

    *up2 = 10;

    cout << *up2 << " sz : "  << sizeof(up2) << endl;

    *up1 = 10; // runtime error - up1은 자원이 up2에 전달 됨. unique_ptr은 int* 사용 대비 성능 저하가 없음.
    // 컴파일 시 모두 inline으로 교체 됨.

}




#include "show.h"
#include <memory>

struct Freer
{
    inline void operator()(void*p)
    {
        cout << "Freer" << endl;
        free(p);
    }
};

// 삭제자 전달 방법
int main()
{
    // 삭제자 전달  : 템플릿 인자로 전다
    // 장점 : 삭제자를 별도로 보관할 필요가 없고, 인라인 치환된다.
    // 단점 : 삭제자가 다르면 다른 타입이 된다. 같은 컨테이너에 보관이 안된다.
    unique_ptr<int> p1(new int);
    unique_ptr<int, Freer> p2((int*)malloc(100)); // unique_ptr은 관리 객체가 없음. 삭제자 지정 방법 필요 ==> template인자로 전달.
    // 탬플릿 인자가 다르면 다른 타입이다.
    
    // shared_ptr<int> p3((int*)malloc(100), foo); // 관리 객체에는 삭제자 정보도 보관.
    // shared_ptr은 왜 다르게 쓰나 ? 삭제자가 다르면 다른 타입이 되기 때문에 함께 vector같은 자료 구조로 묶을 수 없다.
    // shared_ptr<int> p3((int*)malloc(100), foo);

    unique_ptr<int[]> p3(new int[10]); // new T[] 형태면 템플릿 파라미터에 T[]를 넘겨 준다.
}



#include "show.h"
#include <memory>

int main()
{
    shared_ptr<int> sp1(new int);
    unique_ptr<int> up1(new int);

    // 다음 중 에러를 모두 골라 보세요
    shared_ptr<int> sp2 = up1; // ERROR unique_ptr 자원을 공유 하게 되기 때문에 안됨.
    unique_ptr<int> up2 = sp1; // ERROR 나 말고도 다른 놈도 있기 때문에 안됨.

    shared_ptr<int> sp3 = move(up1); // OK : move로 독점권을 포기하는 것이기 때문에 안됨.
    unique_ptr<int> up2 = move(sp1); // ERROR 나 뿐만 아니라, 다른 놈도 가르키고 있을 수 있기 때문에 안됨.
}


쓰레드

#include "show.h"
#include <chrono>
using namespace chrono;


// c++11부터 STL에서 스레드 지원 함.
#include <thread>

void foo(int a, double d)
{
    cout << "foo" << endl;
    this_thread::sleep_for(1min);
}

class FOO {
public:
    void operator()() {
        cout << "FOO" << endl;
    }
    void goo() {
        cout << "goo" << endl;
    }
};

int main()
{
    //thread t(bind(&foo, 1, 3.4)); // 스레드 바로 실행 됨.
    //thread t(&foo, 1, 3.4); // 스레드 바로 실행 됨. c++11 가변인자 템플릿으로 인해 가능 함.
    
    
    // thread t(FOO()); // ERROR 임시 객체 못 받음.
    
    FOO f;
    //thread t(f); // ERROR 임시 객체 못 받음.

    thread t(bind(&FOO::goo, &f)); // bind가 인자 고정뿐만 아니라, 객체 고정도 가능하다.
    t.join(); // 자식 스레드가 죽을 때까지 기다려 줌. 안기다리면 crash.
}


#include "show.h"
#include <chrono>
using namespace chrono;

#include <thread>
#include <mutex>
mutex m;
long x = 10;

void foo()
{
    m.lock();
    x = 100; // 여기서 exception이 난다면 ?  C++에서는 mutex중 exception나면 deadlock
    m.unlock();
}

void foo2()
{
    lock_guard<mutex> lg(m); // lock_guard 생성자에서 m.lock하고 소멸자에서 unlock.
    x = 100; 
}
// C++에서는 코드 플로우 중 exception 발생하면, 로컬 변수는 destroy 한다.
// 즉, try{ } 안에 선언된 lock_guard는 catch에 도달할 때 destroy 됨이 보장 된다.
// Android Framework에서는 lock_guard에 상당하는 Autolock 클래스 지원.

int main()
{
    thread t1(&foo2);
    thread t2(&foo2);

    t1.join();
    t2.join();
}


begin
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

#if 0
// 일반 함수 begin
template<typename T> auto begin(T&c) { return c.begin(); }
template<typename T> auto end(T&c) { return c.end(); }

template<typename T, int N> auto begin(T (&c)[N]) { return c; } // T (&c)[N] 배열 의미
template<typename T, int N> auto end(T (&c)[N]) { return c+N; }
#endif

// c.begin() 대신 begin(c)를 쓰라. 그러면 포인터도 처리 가능. C++11부터 지원
template<typename T> void show(T& c)
{
    auto p = begin(c);
    while (p != end(c)) {
        cout << *p << " ";
        ++p;
    }
    cout << endl;
}

int main()
{
    vector<int> v = { 1,2,3,4,5 };
    show(v);

    int x[5] = { 1,2,3,4,5 };
    show(x); // ??
}

//기타 : c++11은 call by value로 가자라고 함...


해시
#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

int main()
{
    hash<int> hi; // 해쉬 함수(함수 객체로 되어 있음)
    cout << hi(10) << endl;
    cout << hi(20) << endl;
    cout << hi(10) << endl;

    hash<string> hs;
    cout << hs("hello") << endl;
    cout << hs("whatup") << endl;
    cout << hs("hello") << endl;

    cout << hash<double>()(3.4) << endl;
}

#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

int main()
{
    unordered_set<int> s;

    s.insert(10);
    s.insert(30);
    s.insert(15);
    s.insert(20);

    pair<unordered_set<int>::iterator, bool> ret = s.insert(20); // 중복 허용하지 않음. Fail
    if (ret.second == false)
        cout << "실패" << endl;

    // s에서 30을 찾아서 출력
    unordered_set<int>::iterator p = s.find(30);
    cout << *p << endl;


}


#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

struct People
{
    string name;
    int age;

    People(string name = "", int a = 0) : name(n), age(a) {}

};

int main()
{
    set<People> s1;
    s1.insert(People("kim", 10)); // set은 크기 비교 tree다. 거기에 People을 추가하려면, 비교 방법이 필요 함.
                                   // 방법1. People에 < 연산자가 필요하다.
                                   // 방법2. set<People, PeopleComparee> 등으로 비교함수 객체가 있으면 된다.
                                   // 순위는 방법2번이 우선 순위가 높다. 2가 있으면 2로 대체 된다.

    //unordered_set<People> s2;
}


#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

struct People
{
    string name;
    int age;
    People(string n = "", int a = 0) : name(n), age(a) {}
};

// 사용자정의 타입을 넣으려면 2가지 필요 함.
// 1. 해쉬 함수 (함수객체)로 만들어야한다.
// 2. 상등 비교 함수객체가 있어야 한다.

struct PeopleHash
{
    inline size_t operator()(const People&p) const
    {
        return hash<string>()(p.name) + hash<int>()(p.age);
    }
};

struct PeopleEqual {
    inline bool operator() (const People&p1, const People& p2) const
    {
        return p1.name == p2.name && p1.age == p2.age;
    }
};

int main()
{
    unordered_set<People, PeopleHash, PeopleEqual> s1;
    s1.insert(People("kim", 10)); // 되게 하려면 ??
    s1.insert(People("m", 30)); 
    s1.insert(People("park", 40)); 

}



#include "show.h"
#include <set> // RB [tree]
#include <unordered_set> // [hash] Table

struct People
{
    string name;
    int age;
    
    People(string n = "", int a = 0) : name(n), age(a) {}
};

#if 0
template<typename T> class hash {}; //primary template
template<> class hash<int> { // 전문화
    inline size_t operator()(int a) { }
};
template<> class hash<string> { // 전문화
    inline size_t operator()(string a) { }
};
#endif

// C++ 표준의 hash primary template을 People버전으로 [전문화](specialization)
template<> class hash<People> { // 전문화
public:
    inline size_t operator()(const People&p) const
    {
        return hash<string>()(p.name) + hash<int>()(p.age);
    }
};

template<> class equal_to<People>
{
public:
    inline bool operator() (const People&p1, const People& p2) const
    {
        return p1.name == p2.name && p1.age == p2.age;
    }
};

// 사용자정의 타입을 넣으려면 2가지 필요 함.
// 1. 해쉬 함수 (함수객체)로 만들어야한다.
// 2. 상등 비교 함수객체가 있어야 한다.

struct PeopleHash
{
    inline size_t operator()(const People&p) const
    {
        return hash<string>()(p.name) + hash<int>()(p.age);
    }
};

struct PeopleEqual {
    inline bool operator() (const People&p1, const People& p2) const
    {
        return p1.name == p2.name && p1.age == p2.age;
    }
};

int main()
{
    unordered_set<People, PeopleHash, PeopleEqual> s1;
    s1.insert(People("kim", 10)); // 되게 하려면 ??
    s1.insert(People("m", 30));
    s1.insert(People("park", 40));

    // 만약에 
    unordered_set<People> s2;
}

/*
중요 !!! 기본 개념.
template<typename T> class stack{}; // 임의의 타입에 대해 클래스 찍어 냄. (1) primary template
template<typename T> class stack<T*>{}; // 모든 타입은 1번. 포인터일때는 이것 사용. (2) 부분 전문화(특수화, 특화). partial specialization
                                        // 더블 포인터도 여기 탄다.
template<> class stack<int>{}; // int일때는 이것 사용. (3) 전문화(특수화, 특화) 버전 specialization

stack<double> s1;
stack<int*> s2;
stack<int> s2;

*/







알고리즘

#include "show.h"
// 100여개 알고리즘.

// 핵심 1: remove() 등의 알고리즘은 컨테이너 자체의 크기를 줄이지는 않음. 삭제가 아님. 리턴 값만 알려줌 !!
int main()
{
    vector<int> v = { 1, 2,3,4, 5, 3, 7, 8, 3, 10 };
    vector<int>::iterator p = remove(v.begin(), v.end(), 3); // 주어진 범위에서 3을 찾아서 삭제. list, vector, deque, 배열도 됨.
    // 컨테이너를 줄이지는 않음. 나머지 요소를 앞으로 당겨 옴.
    // p는 유효한 값 [다음] 위치를 돌려 줌.

    show(v);
    cout << *p << endl;

    v.erase(p, v.end()); // 여기서 실제 지우는 동작 함 !
    show(v);
}

#include "show.h"
// 총 4가지 변형이 존재 한다 : remove, remove_if, remove_copy, remove_copy_if

using namespace std::placeholders;
int main()
{
    vector<int> v = { 1, 2,3,4, 5, 3, 7, 8, 3, 10 };
    vector<int> v2(10);

    // 알고리즘의 4가지 변경.
    // vector<int>::iterator p = remove(v.begin(), v.end(), 3); // (1) 상수 버전
    // vector<int>::iterator p = remove_if(v.begin(), v.end(), foo); // (2) 조건자 버전 (함수)
    // vector<int>::iterator p = remove_if(v.begin(), v.end(), bind(modulus<int>(), _1, 2)); // (2) 조건자 버전(함수 객체)
    // vector<int>::iterator p = remove_if(v.begin(), v.end(), [](int a) { return a % 2 == 0;}); // (2) 조건자 버전(람다)
    //v.erase(p, v.end());

    // (3) 알고리즘의 복사 버전.
    vector<int>::iterator p = remove_copy(v.begin(), v.end(), v2.begin(), 3); // loop가 1회만 돈다. copy + remove 해도 되나 그보다 빠르다.
    // 여기서 p는 v2의 반복자
    // 반면, sort + copy는 성능차이가 많이 나기 때문에 sort_copy가 없다.
    // show(v);

    //show(v2);
    //v2.erase(p, v2.end());
    //show(v2);

    // (4) 조건자 복사 버전
    vector<int>::iterator p = remove_copy_if(v.begin(), v.end(), v2.begin(), [](int a) { return a % 2 == 0;}); 
}


#include "show.h"

int main() {
    int x[5] = { 1, 2, 3,4, 5 };
    int *p1 = find(x, x + 5, 3); // 상수 버전
    int *p2 = find_if(x, x + 5, [](int a) {return a % 2 == 0;}); // 조건자 버전
    cout << *p2 << endl;

    sort(x, x + 5); // < 연산으로 비교
    sort(x, x + 5, greater<int>()); // >로 비교
    // sort냐 sort_if냐 ? 왜 sort냐? find는 find_if는 같은 인자개수.
    // 조건자 버전이라도 parameter overloading 가능 하면 그대로 쓴다.
}


#include "show.h"
#include <numeric>

// (1) (2) (3) : algorithm 헤더
// (4) : numeric 헤더
int main() {
    int x[5] = { 1,2,3,4,5 };

   find(x, x + 5, 3); // 내부 구조 안바뀜. (1) 변경 불가 sequence 알고리즘.
   reverse(x, x + 5); // 내부 구조 바뀜.   (2) 변경 가능 sequence 알고리즘.
   sort(x, x + 5); //                     (3) 정렬 알고리즘.

   int n = accumulate(x, x + 5, 0); //    (4) 범용 수치 알고리즘.
   cout << n << endl;
}


#include "show.h"
#include <numeric>

// 범용 수치 알고리즘은 연산자를 바꿀 수 있다.
int main() {
    int x[5] = { 1,-2,3,4,5 };
    int y[5] = { 0 };
    int z[5] = { 0 };

    int n = accumulate(x, x + 5, 0); // 기본 버전 : + 사용
    int n2 = accumulate(x, x + 5, 1, multiplies<int>()); // * 사용 1~5까지의 곱 = 5!
    int n3 = accumulate(x, x + 5, 0, [](int a, int b) {return abs(a) + abs(b);}); // abs
    cout << n3 << endl;

    //partial_sum(x, x + 5, y);
    partial_sum(x, x + 5, y, multiplies<int>());
    for (int i = 0; i < 5; i++)
        cout << y[i] << " ";
}

// www.boost.org : 표준과 별도의 라이브러리 만들어보자.
// 아무도 이해할 수 없다 ㅠ;



#include "show.h"
#include <numeric>

int main() {
    vector<int> v = { 1, 2, 3,4, 5 };
    vector<int>::iterator p = copy(v.begin() + 1, v.end(), v.begin());

    show(v);
    v.erase(p, v.end());
    show(v);

    vector<int> v2 = { 1, 2, 3, 4, 5 };
    vector<int>::iterator p2 = copy(v2.begin(), v2.end()-1, v2.begin()+1); 
    show(v2);

    // 예전엔 전부 1일 나왔음. 현재는 뒤에서부터 옴김.
    // 때문에 예전엔 
    vector<int> v3 = { 1, 2, 3, 4, 5 };
    copy_backward(v3.begin(), v3.end() - 1, v3.end());
    show(v3);

    string s = "ABCD";
    do {
        cout << s << endl;
    } while (next_permutation(s.begin(), s.end()));
}


WebKit이나 Chromium에 쓰이는 RefCounted는 아예 RefCount<>를 상속 받아서 쓴다.
http://www.suninf.net/2013/11/refcounted-in-chrome.html
template <class T> class RefCounted; class MyFoo : public base::RefCounted<MyFoo> { protected: ~MyFoo() {} friend class base::RefCounted<MyFoo>; };

 

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

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

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

 

 

출처: https://joycoding.wordpress.com/2016/02/05/%EC%88%9C%ED%99%98%EC%B0%B8%EC%A1%B0-%EB%81%8A%EC%9E%90-breaking-dependency-circle/

 

순환참조 끊자.

작성일자2016년 2월 5일카테고리아키텍처

 
많은 개발자들은 순환 참조(Dependency Circles)가 해롭다는 것은 모두 다 알고 있다.
그런데 자신도 모르게, 일정에 쫓기고, 귀찮아서 등등의 핑계로 오늘도 순환 참조를 만들고 있다.
일단 순환 참조를 생성하는 이유는 접도록 하겠다.

이 이야기는 시작하면 끝이 없기 때문이다.그럼 주제에 맞게 순환 참조를 어떻게 하면 끊을 수 있을까?
순환 참조는 어떻게 생겼나? 한마디로 두개의 클래스가 서로 참조하고 있는 것을 말한다.
쉽게 설명하면 A 클래스는 B 클래스를 호출하고 B 클래스는 A 클래스를 호출한다.

 

 

 

실무에서 이런 경우는 정말 허다하다 못해 널렸다.
해결하는 방법은 아주 쉽다. 너무 쉽다.

1. B의 메소드를 선언한 C 인터페이스는 만든다.
2. B는 C의 구현체로 한다.
3. A는 C를 의존한다.
4. B가 참조하는 A의 메소드는 유지한다.

다시 그림으로 표현하면 아래의 그림과 같이 된다.

 

 

 

이렇게 디자인하면 순환 참조가 없어지게 된다. 야호!!

그런데 우리가 코딩을 하다보면 순환 참조가 필요할 때가 있다.
A에서 실제적으로 B의 참조가 필요한 경우를 말한다.
이럴때는 또 하나의 D 클래스를 생성해서 A와 B를 참조하여 이슈를 해결할 수 있다.

 

 

 

완.전.해.결!
위 그림은 우리가 이미 잘 알고 있는 Dependency Injection 이다.
이런 형태는 프레임워크가 없어도 언어 레벨에서 해결이 가능하다.
좀더 넓은 영역에서 바라본다면 클래스간 참조한다고 모두 순환 참조는 아니다.
하나의 패키지안에서 클래스간 순환 참조는 허용한다.
하지만 패키지가 다른 클래스가 참조하는 것과 Jar간 참조도 마찬가지 완전 피해자.

참고 : https://dzone.com/articles/breaking-dependency-cycles

 

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

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

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

 

 

출처: http://stackoverflow.com/questions/22654422/using-stdshared-ptrvoid-to-point-to-anything

 

You can.

#include <memory> #include <iostream> #include <string> using namespace std; class wild_ptr { shared_ptr<void> v; public: template <typename T> void set(T v) { this->v = make_shared<T>(v); } template <typename T> T & ref() { return (*(T*)v.get()); } }; int main(int argc, char* argv[]) { shared_ptr<void> a; a = make_shared<int>(3); cout << (*(int*)a.get()) << '\n'; cout << *static_pointer_cast<int>(a) << '\n'; // same as above wild_ptr b; cout << sizeof(b) << '\n'; b.set(3); cout << b.ref<int>() << '\n'; b.ref<int>() = 4; cout << b.ref<int>() << '\n'; b.set("foo"); cout << b.ref<char *>() << '\n'; b.set(string("bar")); cout << b.ref<string>() << '\n'; return 0; }

Output:

3 3 8 3 4 foo bar

 

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

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

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

 

 

출처: http://www.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_tutorial&no=206

 

boost의 shared_ptr을 써봤는데 다 좋은데 치명적인 문제점이 있더군요. 
밑에 Lyn님께서 설명해주신 상황이죠. 
근본적으로 아래와 같은 문제입니다. 
 

?

1
2
3
4
TYPE* p = new TYPE;
shared_ptr<type> a(p);
shared_ptr<type> b(p);
</type></type>



이렇게 하면 a와 b가 별개로 존재하게 되죠. 
a의 참조 카운트가 0이 되서 객체가 사라져도 b는 그것을 모르고 
여전히 유효하다고 생각해서 객체에 접근하려고 하거나 이미 지워진 
객체를 또다시 지우는 댕글링 포인터 문제가 발생합니다. 
이 문제를 해결하는 것이 enable_shared_from_this를 상속받아서 
사용하는 것입니다. 
그러나 문제가 있습니다. c++ 빌더는 VCL에서 상속받은 클래스들은 
다중상속을 허용하질 않습니다. 즉 enable_shared_from_this를 상속받는 것 
그 자체가 불가능하게 됩니다. 
저는 제가 만든 새로운 프로그램 패러다임인 관계지향 프로그래밍을 위해서 
모든 클래스에 베이스로 TSocialObject를 깔고 있는데 이것이 TObject에서 
온 것이기 때문에 모든 클래스가 다중상속이 불가능한 상태입니다. 
그리고 제가 열심히 테스트해본 결과 enable_shared_from_this 자체에도 
문제가 있습니다. 
 

?

1
2
3
4
5
6
7
8
9
10
11
12
class TYPE: public enable_shared_from_this<type>
{
};


class TYPE2: public TYPE
{
    TYPE2()
    {
        shared_ptr<type2> p = shared_from_this();
    }
};
</type2></type>



위의 코드는 에러를 발생시킵니다. 
enable_shared_from_this는 안타깝게도 그 능력을 상속시킬 수가 없습니다. 
(템플릿에 의존하기 때문입니다.) 
즉 모든 클래스가 enable_shared_from_this를 직접 상속받아야 합니다. 
 

?

1
2
3
4
5
6
7
8
class TYPE: public enable_shared_from_this<type>
{
};


class TYPE2: public TYPE, public enable_shared_from_this<type2>
{
};
</type2></type>



이런 식으로 말입니다. 
그런데 enable_shared_from_this는 가상 클래스가 아닙니다. 
내부에 멤버변수 weak_this_가 있어서 8바이트를 잡아먹습니다. 
즉 모든 클래스에 enable_shared_from_this를 상속시키면 상속이 누적될수록 
인스턴스에 쓸데없는 쓰레기 메모리 8바이트가 계속 추가로 생긴다는 문제입니다. 
이걸 피하기 위해서 베이스 클래스에서 한번만 멤버변수를 정의하면 
상속받은 클래스에서도 계속 사용할 수 있어야만 한다고 생각했습니다. 

즉 다중상속이 되지 않아도 쓸 수 있어야 하고 
베이스 클래스에서 한번만 정의하면 쓸 수 있도록 한다.. 
정말 어려운 조건이었습니다만 해냈습니다. ㅎㅎ... 
물론 타입을 무시하고 억지로 스태틱 캐스팅을 하기 때문에 감히 권할만한 코드는 아닙니다. 
그리고 enable_shared_from_this 처럼 자동으로 백그라운드에서 동작이 이뤄지질 않습니다. 
제가 boost 코드를 함부로 고칠 수가 없기 때문입니다. 
그러나 동작에는 아무런 문제가 없습니다. 버그도 없습니다. 

구현방법은 2가지가 있습니다. 
첫번째 방법은 자동으로 초기화가 이뤄집니다. 
클래스 선언에다가 매크로만 추가해주면 그걸로 작업은 끝이고 
개발자가 수동으로 초기화 함수같은걸 호출할 필요가 없습니다. 
그러나 이 방법은 오직 shared_from_this() 함수를 사용할 수 있게만 합니다. 
shared_from_this()로 생성된 shared_ptr은 별개의 트리로 존재하기 때문에 
제가 맨위에 설명했던 문제점은 그대로 존재하게 됩니다. 
 

?

1
2
3
4
TYPE* p = new TYPE;
shared_ptr<type> a(p);
shared_ptr<type> b(p);
</type></type>



바로 이 문제점 말입니다. 
즉 보다 편리하고 간단하게 사용할 수 있지만 근본적인 문제는 남아있다는 것입니다. 
아래는 그 구현입니다. 
 

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#define ENABLE_SHARED_FROM_THIS(TYPE)
private:
    boost::shared_ptr<type> shared_from_this()
    {
        return *((boost::shared_ptr<type>*)fake_this.dummy);
    }
    boost::shared_ptr<type const=""> shared_from_this() const
    {
        return *((boost::shared_ptr<type>*)fake_this.dummy);
    }


#define BASE_ENABLE_SHARED_FROM_THIS(TYPE)
private:
    struct fake_shared_from_this
    {
        char dummy[sizeof(boost::shared_ptr<type>)];
        fake_shared_from_this()
        {
            static int diff_between_this = -2;
            if ( diff_between_this < 0 )
            {
                if ( diff_between_this == -2 )
                {
                    diff_between_this = -1;
                    TYPE* test_obj = new TYPE;
                    diff_between_this = (int)(&test_obj->fake_this) - (int)test_obj;
                    delete test_obj;
                }
                else return;
            }


            boost::shared_ptr<type> temp((TYPE*)((int)this - diff_between_this));
            memset(dummy, 0, sizeof(dummy));
            *((boost::shared_ptr<type>*)dummy) = temp;
        }
    };
    friend fake_shared_from_this;
private:
    boost::shared_ptr<type> shared_from_this()
    {
        return *((boost::shared_ptr<type>*)fake_this.dummy);
    }
    boost::shared_ptr<type const=""> shared_from_this() const
    {
        return *((boost::shared_ptr<type>*)fake_this.dummy);
    }
protected:
    fake_shared_from_this fake_this;
    template <class subtype="">
    boost::shared_ptr<subtype> shared_from_this(SUBTYPE* p = NULL)
    {
        return *((boost::shared_ptr<subtype>*)fake_this.dummy);
    }
    template <class subtype="">
    boost::shared_ptr<subtype const=""> shared_from_this(SUBTYPE* p = NULL) const
    {
        return *((boost::shared_ptr<subtype>*)fake_this.dummy);
    }
</subtype></subtype></class></subtype></subtype></class></type></type></type></type></type></type></type></type></type></type></type>



두개의 매크로가 있습니다. 

ENABLE_SHARED_FROM_THIS(TYPE) 
BASE_ENABLE_SHARED_FROM_THIS(TYPE) 

베이스 클래스에서 BASE_ENABLE_SHARED_FROM_THIS를 한번만 사용하면 됩니다. 
그이후 파생 클래스들에서는 ENABLE_SHARED_FROM_THIS 만 사용하면 됩니다. 
아래는 그 예입니다. 
 

?

1
2
3
4
5
6
7
8
9
class test
{
    BASE_ENABLE_SHARED_FROM_THIS(test)
};


class test2 : public test
{
    ENABLE_SHARED_FROM_THIS(test2)
};



이렇게 하면 끝입니다. shared_from_this()를 자유롭게 사용할 수 있습니다. 
그러나 이 방법보다 더 개선된 두번째 방법이 있습니다. 
두번째 방법은 근본적으로 boost 라이브러리 안의 enable_shared_from_this가 사용하는 
방법을 흉내낸 것입니다. 
다만 shared_ptr 자체의 소스를 건드릴 수 없기 때문에 사용하는 방법에 제약이 가해집니다. 
특정한 방법으로 사용해야만 됩니다. 

일단 구현 소스부터 설명합니다. 
 

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#define NEW(TYPE)                    TEnableSharedFromThis<type>::ChangeToSharedPointer(new TYPE)
#define NEW_PARAM(TYPE, constructor) TEnableSharedFromThis<type>::ChangeToSharedPointer(new TYPE constructor)
#define NEW_ARRAY(TYPE, size)        TEnableSharedFromThis<type>::ChangeToSharedPointer(new TYPE[size])


template<class type="">class TEnableSharedFromThis
{
public:
    static boost::shared_ptr<type>ChangeToSharedPointer(TYPE* new_obj)
    {
        boost::shared_ptr<type>p(new_obj);
        boost::weak_ptr<type> *wp = (boost::weak_ptr<type> *) & new_obj->weak_this_;
        if (new_obj->weak_this_.expired())
            new_obj->weak_this_ = p;
        return p;
    }
};


#define ENABLE_SHARED_FROM_THIS(TYPE) \
private:
    boost::shared_ptr<type> shared_from_this()
    {
        boost::weak_ptr<type>* wp = (boost::weak_ptr<type>*) &weak_this_;
        return boost::shared_ptr<type>(*wp);
    }
    boost::shared_ptr<type const=""> shared_from_this() const
    {
        boost::weak_ptr<type>* wp = (boost::weak_ptr<type>*) &weak_this_;
        return boost::shared_ptr<type const="">(*wp);
    }


#define BASE_ENABLE_SHARED_FROM_THIS(TYPE)
public:
    boost::weak_ptr<type> weak_this_;
private:
    boost::shared_ptr<type> shared_from_this()
    {
        boost::weak_ptr<type>* wp = (boost::weak_ptr<type>*) &weak_this_;
        return boost::shared_ptr<type>(*wp);
    }
    boost::shared_ptr<type const=""> shared_from_this() const
    {
        boost::weak_ptr<type>* wp = (boost::weak_ptr<type>*) &weak_this_;
        return boost::shared_ptr<type const="">(*wp);
    }
protected:
    template <class subtype="">
    boost::shared_ptr<subtype> shared_from_this(SUBTYPE* p = NULL)
    {
        return boost::shared_ptr<subtype>(*(boost::weak_ptr<subtype>*)&weak_this_);
    }
    template <class subtype="">
    boost::shared_ptr<subtype const=""> shared_from_this(SUBTYPE* p = NULL) const
    {
        return boost::shared_ptr<subtype const="">(*(boost::weak_ptr<subtype>*)&weak_this_);
    }
</subtype></subtype></subtype></class></subtype></subtype></subtype></class></type></type></type></type></type></type></type></type></type></type></type></type></type></type></type></type></type></type></type></type></type></class></type></type></type>



BASE_ENABLE_SHARED_FROM_THIS과 ENABLE_SHARED_FROM_THIS 
이 2개의 매크로를 사용하는 것은 똑같습니다. 
그러나 이제 new를 사용하면 안됩니다. 
대신 만든 NEW 매크로를 사용해서 인스턴스를 생성해야만 합니다. 
NEW매크로는 인스턴스 안의 weak_this_ 멤버변수 초기화도 함께 합니다. 
NEW매크로만 써주시면 enable_shared_from_this 를 사용하는 것과 
똑같은 수준으로 shared_ptr 관리가 됩니다. 

단 주의하실 것이 있습니다. 
첫번째 구현방법은 초기화가 자동이기 때문에 new로 만들지 않은 객체에서도 
shared_from_this()를 사용할 수 있었습니다. 그러나 두번째 구현방법은 
new로 만든 객체만 shared_from_this()를 쓸 수 있습니다. 
제가 만든 것뿐만 아니라 boost 라이브러리가 제공하는 enable_shared_from_this 
역시 new로 만든 객체에서만 shared_from_this()를 사용할 수 있는건 마찬가지입니다. 
제가 못해서 그런게 아니니까 이해해 주시길... 

shared_ptr은 정말 좋은 클래스입니다. 
이걸 본격적으로 쓰면 C++로도 자바처럼 메모리에 대해 신경 안쓰고 코딩하는게 
완전히 가능하겠더군요. 
여러분도 shared_ptr의 세계로 빠져들어보세요. 
오랫만에 c++에 푹 빠져봤습니다. 맨날 c만 하다가 c++ 코딩 본격적으로 다시 해본게 
7년? 8년만이군요.. 
그럼 수고하세요.

 

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

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

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

 

[출처] https://202psj.tistory.com/1408

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
266 [C/C++ 자료구조] 아스키 코드표(ASCII Table) file 졸리운_곰 2023.08.16 6
265 [WSL2] WSL2 외부 접속 설정 file 졸리운_곰 2023.06.26 39
264 [WSL2] Mysql 자동실행 설정하기 file 졸리운_곰 2023.06.25 15
263 [visual studio] Bring Your MFC Application to the Web mfc 어플리케인 web 구동 file 졸리운_곰 2023.03.20 4
262 [C/C++][인터넷] [C++] Full-fledged client-server example with C++ REST SDK 2.10 졸리운_곰 2023.02.20 23
261 [Linux programming][turbo c] The libXbgi Library file 졸리운_곰 2023.02.05 9
260 [visual studio] [borland c] Using the WinBGIm Graphics Library with Visual Studio 2005/2008 2010 file 졸리운_곰 2023.02.05 4
259 [WSL2] 윈도우에서 linux 사용 (WSL 2), xwindows GUI 설정 file 졸리운_곰 2023.01.28 18
258 [WSL2] Windows 11의 WSL2에서 리눅스 X Window 응용프로그램 실행하기 file 졸리운_곰 2023.01.28 22
257 [C/C++ 인공지능] Getting Started with mlpack 졸리운_곰 2023.01.28 12
256 [리눅스][linux] Ubuntu/Linux에서 user password를 짧거나 쉬운 password로 변경하는 방법. file 졸리운_곰 2023.01.08 6
255 [linux master][리눅스 마스터][국가기술자격] [GCP 원데이] 서버가 죽는 이유, Message Queue file 졸리운_곰 2023.01.01 8
254 [linux master][리눅스 마스터][국가기술자격] 리눅스 서버 다운 원인 5가지 졸리운_곰 2023.01.01 3
253 [WSL2] WSL에서의 Jupyter notebook 사용하기. file 졸리운_곰 2022.11.27 40
252 [docker] [Oracle] docker에 Oracle 11g 설치하기 file 졸리운_곰 2022.11.26 15
251 [linux master][리눅스 마스터][국가기술자격] Shell In A Box-원격 Linux 서버에 액세스하기위한 웹 기반 SSH 터미널 file 졸리운_곰 2022.11.17 4
250 [linux dev env] [우분투 서버] noVNC 접속 file 졸리운_곰 2022.11.16 3
249 [C/C++][인터넷] [C++] FTP Upload/Download 구현 클래스(매우 유용) file 졸리운_곰 2022.11.16 23
248 [리눅스 일반] ffmpeg에서 m4a 파일을 mp3 파일로 변환할때 생기는 오류에 관하여 file 졸리운_곰 2022.11.11 11
» [C/C++ 언어일반] C/C++ 스마트 포인터 관련 file 졸리운_곰 2022.11.06 22
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED