2013-04-08 9 views
5

Piszę kod inteligentnych wskaźników jako ćwiczenie. Korzystanie z samouczków online (1, 2) Opracowałem normalną klasę inteligentnych wskaźników z licznikiem odwołań. Problemem jest to, nie jestem w stanie dowiedzieć się, co następuje:Unikaj krojenia obiektów dla nie-wirtualnych destruktorów.

gdy inteligentny wskaźnik wykryje, że nie ma więcej odniesień istnieć do danego obiektu, należy go usunąć obiekt za pomocą wskaźnika do samego typu, nawet jeśli argument szablonu ostatecznego wskaźnika inteligentnego jest typu podstawowego. Ma to na celu uniknięcie krojów obiektów dla nie-wirtualnych destruktorów.

Jak mogę to osiągnąć. Zasadniczo mój kod wygląda jak poniżej (z samouczka).

template < typename T > class SP 
{ 
private: 
    T* pData;  // pointer 
    RC* reference; // Reference count 

public: 
    SP() : pData(0), reference(0) 
    { 
     // Create a new reference 
     reference = new RC(); 
     // Increment the reference count 
     reference->AddRef(); 
    } 

    SP(T* pValue) : pData(pValue), reference(0) 
    { 
     // Create a new reference 
     reference = new RC(); 
     // Increment the reference count 
     reference->AddRef(); 
    } 

    SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference) 
    { 
     // Copy constructor 
     // Copy the data and reference pointer 
     // and increment the reference count 
     reference->AddRef(); 
    } 

    ~SP() 
    { 
     // Destructor 
     // Decrement the reference count 
     // if reference become zero delete the data 
     if(reference->Release() == 0) 
     { 
      delete pData; 
      delete reference; 
     } 
    } 

    T& operator*() 
    { 
     return *pData; 
    } 

    T* operator->() 
    { 
     return pData; 
    } 

    SP<T>& operator = (const SP<T>& sp) 
    { 
     // Assignment operator 
     if (this != &sp) // Avoid self assignment 
     { 
      // Decrement the old reference count 
      // if reference become zero delete the old data 
      if(reference->Release() == 0) 
      { 
       delete pData; 
       delete reference; 
      } 

      // Copy the data and reference pointer 
      // and increment the reference count 
      pData = sp.pData; 
      reference = sp.reference; 
      reference->AddRef(); 
     } 
     return *this; 
    } 
}; 

EDIT:

Aby to osiągnąć muszę mieć wskaźnik do typu oryginału.

I napisali tutaj pytanie: delete via a pointer to Derived, not Base

ale teraz od przeglądania komentarzy i odpowiedzi Myślę, że obie są powiązane. Mam konstruktora:

template <typename T> 
template <typename U> 
Sptr<T>::Sptr(U* u) : obj(u),ref(NULL) { 
    //do something 
    ref = new RC(); 
    ref->AddRef(); 
} 

Rozważmy teraz Sptr<Base1> sp(new Derived); gdzie Derived pochodzi z Base1. Baza1 ma chroniony konstruktor/destruktor. Który jest przechowywanie dla obiektu typu T Ale muszę przechowywać go za pośrednictwem obiektu typu U. Muszę zachować to. Jak mogę to zrobić?

+0

Jeśli klasa bazowa nie ma wirtualnego destruktora, a ktoś próbuje usunąć pochodną klasę przez wskaźnik do tej klasy bazowej, to ktoś robi to źle. – Chad

+2

Aby to osiągnąć, musisz nadać 'SP' konstruktorowi szablonu' SP :: SP (U * u) {...} 'i jakoś przechowywać oryginalny typ' U' (który musi pochodzić od ' T'), aby móc później wywołać destruktor 'U'. – Angew

+0

Czy C++ 11 stwierdza, że ​​inteligentny wskaźnik zgodny musi to zrobić? Wygląda na to, że 'std :: unique_ptr' nie: http://ideone.com/iyanmY – Chad

Odpowiedz

6

Twoja inteligentna wskazówka potrzebuje 3 porcji informacji.

Najpierw wskaźnik do danych (T* lub coś podobnego).

Po drugie, licznik odniesień: std::atomic<int> czy coś.

Po trzecie, twoja funkcja niszczenia (std::function<void(T*)> lub coś podobnego).

Po utworzeniu inteligentnego wskaźnika tworzona jest funkcja niszczenia. Kiedy twój inteligentny wskaźnik zostanie skopiowany do innego inteligentnego wskaźnika, funkcja niszczenia jest kopiowana. Jeśli typ nowego inteligentnego wskaźnika nie pasuje do starego, ta funkcja zniszczenia jest zapakowana w sposób zgodny z typem (czy std::function<void(Base*)> = std::function<void(Derived*)> działa po wyjęciu z pudełka? Bez względu na to, w zasadzie to robisz).

Domyślnie ta funkcja niszczenia jest po prostu delete t, ale jako korzyść uboczna, umożliwia to użytkownikom inteligentnego wskaźnika przekazanie funkcji niszczenia, która nie zawsze jest delete t.

Zabawnie, w zastępstwie reset, zastępujesz funkcję niszczenia. Możesz więc uczynić sygnaturę funkcji niszczenia jako std::function<void()>, co sprawia, że ​​przenoszenie jej między inteligentnymi wskaźnikami typu T i U jest łatwiejsze.

template < typename T > class SP 
{ 
private: 
    T* pData;  // pointer 
    RC* reference; // Reference count 
    std::function<void()> destroyData; 
public: 
    template<typename U> 
    SP(U* pValue): 
    pData(pValue), 
    reference(nullptr), 
    // store how to destroy pValue now, for later execution: 
    destroyData([pValue]()->void{ 
     delete pValue; 
    }) 
    { 
    // Create a new reference 
    reference = new RC(); 
    // Increment the reference count 
    reference->AddRef(); 
    } 
    // similar for operator=, and you may have to do something for SP<T> as well: 
    template<typename U> 
    SP(const SP<U>& sp): 
    pData(sp.pData), 
    reference(sp.reference), 
    destroyData(sp.destroyData) 
    { 
    // Copy constructor 
    // Copy the data and reference pointer 
    // and increment the reference count 
    reference->AddRef(); 
    } 
    template<typename U> 
    SP<T>& operator = (const SP<U>& sp) 
    { 
    // blah blah blah, then 
    destroyData = sp.destroyData; 
    } 

    ~SP() 
    { 
    // Destructor 
    // Decrement the reference count 
    // if reference become zero delete the data 
    if(reference->Release() == 0) 
    { 
     delete reference; 
     destroyData(); // here I destroyed it! 
    } 
    } 
}; 

czy coś takiego

+0

jak mogę zapisać wskaźnik do właściwej funkcji niszczenia. Czy możesz być trochę bardziej konkretny, ponieważ jestem nowy w tym stylu kodowania szablonów. – footy

+0

Zawarto¶ć fragmentu kodu zawierającego ilustrację. Uwzględniłem także rozpoczęcie pracy z wskaźnikami innymi niż "T *" do skonstruowania i typy inne niż "SP " przypisane do 'SP '. To nie jest kompletne, ale mam nadzieję, że wpadniesz na ten pomysł. Dobrą rzeczą, o której możesz pomyśleć, jest ostatecznie użycie SFINAE, aby poprawne typy 'U' zawierały tylko te, które pochodzą z' T' w punkcie, w którym występuje przeciążenie, ale jest to zaawansowana technika. – Yakk

+0

Dzięki temu uratowałem mi życie: P – footy

0

Alternatywne podejście polega delegowanie skreślenia inną klasę

// non templated base 
class DeleterBase { 
    public: 
     virtual ~DeleterBase() { }; 
}; 

template <typename T> 
class Deleter : public DeleterBase { 
    private: 
     T *ptr; 
    public: 
     Deleter(T *p) // remember the pointer with the correct type here 
      : ptr{p} 
     { } 

     ~Deleter() { 
      delete this->ptr; // invokes correct destructor 
     } 
}; 

teraz w inteligentny wskaźnik:

template <typename T> 
class SP { 
    private: 
     T *p; 
     DeleterBase *deleter; 
    public: 
     template <typename U> // U is deduced to actual type 
     explicit SP(U *p) 
      : p{p}, 
      deleter{new Deleter<U>(p)} // correct type 
     { } 

     // transfer control in the move constructor/assignment 
     // increase the ref count in copy constructor/assignment 

     // now just delete the deleter in the dtor 
     ~SP() { 
      if (reference_count_is_zero()) { // however you implement this 
       delete this->deleter; 
      } 
     } 
}; 

Ponieważ masz już klasę RC, możesz przenieść funkcjonalność, którą tu pokazałem, do tej klasy. Niech ta klasa zostanie szablonowa z niesformatowaną bazą i zniszcz wskaźnik, gdy licznik odwołań zostanie usunięty. Jeszcze jedno mniej do przekazania.

Powiązane problemy