2014-12-09 8 views
4

shared_ptr을 직접 구현하려고합니다. make_shared에 문제가 있습니다. std::make_shared의 주요 기능은 연속 블록 메모리에 카운터 블록과 개체를 할당하는 것입니다. 내가 어떻게 할 수 있니? 나는 개체 및 카운터 블록을 삭제할 때Make_shared - own implementation

template<class T> 
class shared_ptr 
{ 
private: 
    class _ref_cntr 
    { 
    private: 
     long counter; 

    public: 
     _ref_cntr() : 
      counter(1) 
     { 
     } 

     void inc() 
     { 
      ++counter; 
     } 

     void dec() 
     { 
      if (counter == 0) 
      { 
       throw std::logic_error("already zero"); 
      } 

      --counter; 
     } 

     long use_count() const 
     { 
      return counter; 
     } 
    }; 

    template<class _T> 
    struct _object_and_block 
    { 
     _T object; 
     _ref_cntr cntr_block; 

     template<class ... Args> 
     _object_and_block(Args && ...args) : 
      object(args...) 
     { 
     } 
    }; 

    T* _obj_ptr; 
    _ref_cntr* _ref_counter; 

    void _check_delete_ptr() 
    { 
     if (_obj_ptr == nullptr) 
     { 
      return; 
     } 

     _ref_counter->dec(); 

     if (_ref_counter->use_count() == 0) 
     { 
      _delete_ptr(); 
     } 

     _obj_ptr = nullptr; 
     _ref_counter = nullptr; 
    } 

    void _delete_ptr() 
    { 
     delete _ref_counter; 
     delete _obj_ptr; 
    } 

    template<class _T, class ... Args> 
    friend shared_ptr<_T> make_shared(Args && ... args); 

public: 
    shared_ptr() : 
     _obj_ptr(nullptr), 
     _ref_counter(nullptr) 
    { 
    } 

    template<class _T> 
    explicit shared_ptr(_T* ptr) 
    { 
     _ref_counter = new counter_block(); 
     _obj_ptr = ptr; 
    } 

    template<class _T> 
    shared_ptr(const shared_ptr<_T> & other) 
    { 
     *this = other; 
    } 

    template<class _T> 
    shared_ptr<T> & operator=(const shared_ptr<_T> & other) 
    { 
     _obj_ptr = other._obj_ptr; 
     _ref_counter = other._ref_counter; 

     _ref_counter->inc(); 

     return *this; 
    } 

    ~shared_ptr() 
    { 
     _check_delete_ptr(); 
    } 

}; 

template<class T, class ... Args> 
shared_ptr<T> make_shared(Args && ... args) 
{ 
    shared_ptr<T> ptr; 
    auto tmp_object = new shared_ptr<T>::_object_and_block<T>(args...); 
    ptr._obj_ptr = &tmp_object->object; 
    ptr._ref_counter = &tmp_object->cntr_block; 

    return ptr; 
} 

는하지만, 잘못된 힙 블록 예외가 발생합니다

내가 그런 일을 시도했다.

+3

공유 포인터 관리를 둘러싸고있는 두 가지 필수 코드는 (a) 참조 계산 알고리즘과 (b) 실제 * 삭제 *로이 게시물에 포함시키지 마십시오. 우린 독자가 아니에요. 실제 문제를 보여주는 [** complete MCVE **] (http://stackoverflow.com/help/mcve)를 게시하십시오. "객체와 카운터 블록을 삭제할 때 ..."라는 문장은 절대로 직접적으로 할당되지 않은 두 가지를 삭제할 것을 제안합니다 (사실 'tmp_object'는 전혀 유지되지 않습니다). – WhozCraig

+0

@WhozCraig 고마워요! 게시물을 업데이트했습니다. –

+2

구현에 몇 가지 주요 기능이 없습니다. 코드를 추가하면 기존 코드에서 솔루션이 나타날 수 있습니다. 무엇이 빠졌는가 : deleter를위한 타입 지우기, 관리 객체와 관련없는 포인터를 저장한다. (thread-safe ref 카운팅, weak ref 카운팅, ..)'shared_ptr '은 복잡하다. 현재 양식. – dyp

답변

8

N.B. _Treserved name 그리고 당신은 문제가 여기에있다 등 자신의 유형/변수/매개 변수

의 이름을 사용하지합니다

void _delete_ptr() 
{ 
    delete _ref_counter; 
    delete _obj_ptr; 
} 

이, 당신은하지 않았다 때문에 make_shared 경우에 잘못 두 개의 개별 오브젝트를 할당하십시오.

Boost 's와 GCC의 shared_ptr에서 make_shared에 대한 접근법은 기본 클래스에 참조 카운트를 포함하고 파생 된 유형의 관리 객체에 대한 저장 공간을 추가하는 새로운 파생 형식의 제어 블록을 사용하는 것입니다. _ref_cntr을 가상 함수를 통해 개체 삭제를 담당하게 만들면 파생 형식이 해당 가상 함수를 재정의하여 다른 작업을 수행 할 수 있습니다 (예 : 명시 적 소멸자 호출을 사용하여 저장소를 비우지 않고 개체를 파괴 함). 당신이 _ref_cntr을주는 경우에

는 가상 소멸자는 delete _ref_counter 제대로 파생 된 형식을 파괴 할 것이다, 그래서 뭔가처럼 될해야 : 당신이 weak_ptr 지원을 추가 할 계획이없는 경우

void _delete_ptr() 
{ 
    _ref_counter->dispose(); 
    delete _ref_counter; 
} 

다음 필요가 없지만 관리 객체와 제어 블록의 파괴를 분리하기 위해, 당신은 단지 제어 블록의 소멸자이 두 가지를 모두 수행 할 수 있습니다 :

void _delete_ptr() 
{ 
    delete _ref_counter; 
} 

현재 디자인 가져 오기를 지원하지 개미 속성이 shared_ptr인데 이는 template<class Y> explicit shared_ptr(Y* ptr) 생성자가 ptr이라는 원래 유형을 기억해야하며 _obj_ptr (T*으로 변환 됨)이 아니라 삭제를 호출해야합니다. boost::shared_ptr의 해당 생성자에 대한 문서의 note을 참조하십시오. 이 작업을 수행하려면 _ref_cntr은 개체의 _obj_ptr과 분리 된 원래 포인터를 저장하기 위해 형식 지우기를 사용해야하므로 _ref_cntr::dispose()이 올바른 값을 삭제할 수 있습니다. aliasing constructor을 지원하려면 설계 변경이 필요합니다. 할당 된 제어 블록에

template<class T, class ... Args> 
shared_ptr<T> make_shared(Args && ... args) 
{ 
    shared_ptr<T> ptr; 
    auto tmp_object = new shared_ptr<T>::_object_and_block<T>(args...); 
    ptr._obj_ptr = &tmp_object->object; 
    ptr._ref_counter = tmp_object; 

    return ptr; 
} 

그래서 _ref_counter 점을 당신이 delete _ref_counter을 할 때 당신이 할당 정확하게 일치 new/delete 쌍을 가지고 있고 의미 :이 디자인으로

class _ref_cntr 
{ 
private: 
    long counter; 

public: 
    _ref_cntr() : 
     counter(1) 
    { 
    } 

    virtual ~_ref_cntr() { dispose(); } 

    void inc() 
    { 
     ++counter; 
    } 

    void dec() 
    { 
     if (counter == 0) 
     { 
      throw std::logic_error("already zero"); 
     } 

     --counter; 
    } 

    long use_count() const 
    { 
     return counter; 
    } 

    virtual void dispose() = 0; 
}; 

template<class Y> 
struct _ptr_and_block : _ref_cntr 
{ 
    Y* _ptr; 
    explicit _ptr_and_block(Y* p) : _ptr(p) { } 
    virtual void dispose() { delete _ptr; } 
}; 

template<class Y> 
struct _object_and_block : _ref_cntr 
{ 
    Y object; 

    template<class ... Args> 
    _object_and_block(Args && ...args) : 
     object(args...) 
    { 
    } 

    virtual void dispose() { /* no-op */ } 
}; 

, make_shared이된다 new으로 하나의 객체를 만든 다음 두 개의 다른 객체를 delete에 시도하는 대신 동일한 객체를 할당 해제합니다.첫 번째 카운트 ( dec()에서 예) 제로 만 호출에 갈 때

당신이 제어 블록에 두 번째 수를 추가하고, 소멸자에서 dispose()에 전화를 이동해야하므로이 호출 weak_ptr 지원을 추가하려면 두 번째 카운트가 0이 될 때 소멸자. 그런 다음 스레드 안전 방법으로 모든 작업을 수행하면이 대답보다 설명하는 데 훨씬 시간이 오래 걸리는 미묘한 복잡성이 추가됩니다.

또한, 구현의이 부분은 잘못된 것입니다 및 메모리 누수 :

void _check_delete_ptr() 
{ 
    if (_obj_ptr == nullptr) 
    { 
     return; 
    } 

그것은 널 포인터, 예를 들어,와 shared_ptr을 생성자에 가능 shared_ptr<int>((int*)nullptr) 인 경우 생성자가 컨트롤 블록을 할당하지만 _obj_ptr이 null이므로 컨트롤 블록을 절대 삭제하지 않습니다.

+1

와우! 놀랍다. 하지만 weak_ptr 지원에 대한 질문이 있습니다. 'shared_ptr'이 없으면 _obj_ptr을 삭제해야합니다. 그러나 여전히'weak_ptr'가있는 경우'_ref_cntr' 객체를 유지해야합니다. 맞습니까? 그러나이'_ref_cntr'이'_object_and_block' 일 때 나는 _ref_cntr을 삭제하지 않고 _obj_ptr을 삭제할 수 없습니다. –

+1

네, 맞습니다. GCC의 구현에서'_object_and_block'에 해당하는 멤버는'Y' 타입의 멤버가 아니고 대신에'std :: aligned_storage :: type' 타입의 메모리를 가지고 있고,'Y' 그 버퍼에서 새로운 배치를 사용하고'dispose()'는'reinterpret_cast (& buffer) -> ~ Y()'를 호출하여 그것을 파괴합니다. 이것은'Y' 객체의 수명을 포함하는'_object_and_block'의 수명보다 더 빨리 끝납니다. –

+0

그래서, GCC에서도, 마지막 'weak_ptr'이 죽을 때에 만 객체의 메모리가 해제됩니까? –