2013-04-08 5 views
5

Ich schreibe Code für Smartpointer als Übung. Tutorials online verwenden (1, 2) Ich habe eine normale Smart-Pointer-Klasse mit Referenzzählung entwickelt. Das Problem ist, ich bin nicht in der Lage die folgenden herauszufinden:Vermeiden Objekt Slicing für nicht-virtuelle Destruktoren

, wenn der Smart-Pointer erkennt, dass keine weiteren Hinweise auf ein bestimmtes Objekt vorhanden ist, muss er das Objekt über einen Zeiger auf den ursprünglichen Typen löschen, auch wenn Das Template-Argument des letzten intelligenten Zeigers ist vom Basistyp. Dies dient dazu, Objekt-Slicing für nicht-virtuelle Destruktoren zu vermeiden.

Wie kann ich das erreichen? Grundsätzlich sieht mein Code wie folgt aus (aus dem Tutorial).

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:

Um das zu erreichen, dass ich einen Zeiger auf den ursprünglichen Typ haben müssen.

ich eine Frage hier gepostet haben: delete via a pointer to Derived, not Base

Aber jetzt, da die Kommentare und Antworten sehen glaube ich, beide bezogen sind. Ich habe den Konstruktor:

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

Betrachten wir nun Sptr<Base1> sp(new Derived); wo Derived von Base1 stammt. Base1 hat einen geschützten Konstruktor/Destruktor. Welche speichert für ein Objekt vom Typ T Aber ich muss es durch ein Objekt des Typs U speichern. Ich muss das bewahren. Wie kann ich das machen?

+0

Wenn eine Basisklasse keinen virtuellen Destruktor hat und jemand versucht, eine abgeleitete Klasse durch einen Zeiger auf diese Basisklasse zu löschen, macht das jemand falsch. – Chad

+2

Um dies zu erreichen, müssen Sie 'SP' einen Template-Konstruktor' SP ​​ :: SP (U * u) {...} 'geben und irgendwie den ursprünglichen Typ' U' speichern (der von 'abgeleitet werden muss T'), um später 'U's Destruktor aufrufen zu können. – Angew

+0

Gibt das C++ 11 an, dass ein kompatibler intelligenter Zeiger das tun muss? Es scheint, dass 'std :: unique_ptr' nicht: http://ideone.com/iyanmY – Chad

Antwort

6

Ihr Smart Pointer benötigt 3 Informationen.

Zuerst der Zeiger auf die Daten (T* oder etwas).

Zweitens, Ihre Referenz zählen: std::atomic<int> oder so etwas.

Drittens, Ihre Vernichtungsfunktion (std::function<void(T*)> oder etwas).

Wenn der Smart Pointer zum ersten Mal erstellt wird, wird diese Destruktionsfunktion erstellt. Wenn Ihr Smart Pointer in einen anderen Smart Pointer kopiert wird, wird diese Destruktionsfunktion kopiert. Wenn der Typ des neuen intelligenten Zeigers nicht mit dem alten übereinstimmt, wird diese Vernichtungsfunktion typkompatibel verpackt (funktioniert std::function<void(Base*)> = std::function<void(Derived*)> out-of-the-box? Unabhängig davon tun Sie das grundsätzlich).

Standardmäßig ist diese Destruktionsfunktion nur delete t, aber als Nebeneffekt erlaubt dies den Benutzern Ihres Smartpointers, eine Vernichtungsfunktion zu übergeben, die nicht immer delete t ist.

Amüsant, auf dem Äquivalent von reset, ersetzen Sie Ihre Vernichtungsfunktion. So können Sie tatsächlich die Signatur der Zerstörungsfunktion std::function<void()> machen, was es einfacher macht, sie zwischen T und U Smartpointern zu verschieben.

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! 
    } 
    } 
}; 

oder so ähnlich

+0

Wie kann ich einen Zeiger auf die richtige Vernichtungsfunktion speichern? Könnten Sie ein wenig genauer sein, da ich diese Art der Template-Codierung neu bin. – footy

+0

@footy Codeschnipsel, das die Technik enthält. Ich habe auch den Beginn des Umgangs mit Nicht-'T *' Zeigern zum Konstruieren und Nicht-'SP 'Typen, die dem' SP ' zugeordnet sind, eingefügt. Es ist nicht vollständig, aber ich hoffe, Sie bekommen die Idee. Eine gute Sache, über die Sie nachdenken könnten, ist schließlich, SFINAE zu verwenden, um die gültigen U-Typen nur diejenigen zu machen, die von T an der Überladungsauflösung abstammen, aber das ist eine fortgeschrittene Technik. – Yakk

+0

Danke das hat mein Leben gerettet: P – footy

0

Ein alternativer Ansatz eine andere Klasse, das Löschen

// 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 
     } 
}; 

jetzt in dem Smartpointer beinhaltet Delegieren:

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; 
      } 
     } 
}; 

Da Sie bereits die RC-Klasse haben, könnten Sie die hier gezeigte Funktionalität in diese Klasse verschieben. Machen Sie diese Klasse zu einem Template mit einer nicht-templateten Basis und zerstören Sie den Zeiger, wenn der Referenzzähler gelöscht ist. Eine Sache weniger, um herumzugehen.