2016-02-09 9 views
5

Löschen habe ich eine generische Klasse, die etwa wie folgt aussieht:einen Vorlagentyp

template <class T> 
class Example 
{ 
    private: 
    T data; 
    public: 
    Example(): data(T()) 
    Example(T typeData): data(typeData) 
    ~Example() 

    // ... 
}; 

Ich bin ein wenig verwirrt darüber, wie eine Deconstructor für so etwas zu realisieren. Insbesondere, da T von einem beliebigen Typ ist, könnte es Speicher zugeordnet auf dem Stapel sein (was immer der Fall für Example ist, die über den Konstruktor ohne Argumente erstellt werden) oder auf dem Heap.

Zum Beispiel, wenn der Kunde macht den Typen für T ein int* und liefert einen Zeiger auf dynamische Speicher, wie kann ich wissen delete auf data zu nennen, im Gegensatz zu, wenn der Kunde den Typen int eingestellt?

+3

Nun, Sie können wie die Standard-Container sein und nicht das tun. Standard-Container-Destruktoren werden nichts an den Elementen vornehmen, wenn der Typ ein Zeigertyp ist. – NathanOliver

+0

Hinweis: Verwenden Sie ein CRTP-Richtlinien-Typ-Idiom, das auf 'std :: is_pointer ' basiert. Viel Spaß beim Aufbau, dann binde die ganze Sache, da es * wirklich * schwierig ist, Zeiger auf automatische Variablen von dynamischen am Verwendungsort zu unterscheiden. – Bathsheba

+0

Beide gültigen Punkte. Ich arbeite an etwas, was ich eher als Übung betrachten würde, also habe ich mich letztendlich gefragt, ob ich etwas verpasst habe, oder ob das nur eine schwierige Sache war, die allgemein zu funktionieren scheint (was der Fall zu sein scheint) – nmagerko

Antwort

7

Die einfachste Antwort ist: nicht. Versuchen Sie nicht, den Benutzer zu erraten und etwas zu tun, was er nicht erwartet. Nehmen Sie dieselbe Richtlinie wie Standardcontainer an: Nehmen Sie an, T räumt nach sich korrekt auf.

Wenn der Clientcode korrekt geschrieben wird, verwendet er RAII-Klassen (z. B. Smartpointer) für die automatische und korrekte Verwaltung von Arbeitsspeicher und anderen Ressourcen. Wenn nicht, können Sie nicht hoffen, das in Ihrem Provider-Code zu beheben.

Machen Sie Ihre Klasse mit std::unique_ptr und std::shared_ptr sowie jeder anderen benutzerdefinierten RAII-Klasse arbeiten und lassen Sie Ihre Kunden die Verwaltung selbst durchführen. Was, wenn sie doch nicht-besitzende Zeiger speichern wollen?

+0

Fair genug - diese Zweideutigkeit von mir, die Reinigung gegen den Kunden zu verwalten, war wirklich, worum ich besorgt war. – nmagerko

2

Sie können die Vorlagenspezialisierung verwenden.

template <class T> 
class Example 
{ 
    private: 
    T data; 
    public: 
    Example() 
     : data(T()) 
    {} 

    Example(T typeData): data(typeData) 
    {} 
}; 

template <class T> 
class Example<T*> 
{ 
    private: 
    T* data; 
    public: 
    Example() : data(nullptr){} 
    Example(T* typeData): data(typeData) {} 
    ~Example() 
    { 
     delete data; 
    } 
}; 

int main() 
{ 
    Example<int> e; 
    Example<int*> e2; 

    return 0; 
} 
+1

Cooles Muster. Es ist viel einfacher, das Verhalten auf diese Weise zu dokumentieren. – nmagerko

+1

Ich würde vorsichtig sein, dies im Produktionscode zu verwenden. Wie andere gesagt haben: Es ist schwierig zu sagen, ob ein Zeiger auf 'neu' Speicher zeigt oder nicht. –

0

Sie könnten sich einfach nicht darum kümmern wie die Standardbibliothek. Wenn Sie z. B. einen Vektor von Zeigern erstellen, sind Sie dafür verantwortlich, diese zu löschen, bevor Sie zulassen, dass der Vektor den Gültigkeitsbereich verlässt. Die Leute können dann entscheiden, ob sie überhaupt überhaupt gelöscht werden wollen (vielleicht ist es zum Sortieren temporär, und etwas anderes besitzt das Objekt). Sie können auch intelligente Zeiger verwenden, so dass der Vektor das Objekt über den Destruktor für den intelligenten Zeiger zerstört.

In diesem Fall ist weniger mehr. Du musst nichts Kompliziertes machen. Sie müssen nicht mehrere Versionen der Vorlage verwalten. Schließlich hat der Benutzer Ihrer Vorlage mehr Kontrolle ... und natürlich auch Verantwortung.

0

Ich empfehle Ihnen, std::unique_ptr für T zu verwenden, wenn Sie Example benötigen, um einen besitzenden Zeiger zu halten. Wenn T ein roher Zeiger ist, dann ist es einfach nicht Eigentümer und sollte es nicht löschen.

Wenn Sie Example benötigen, um den Zeiger zu initialisieren, spezialisieren Sie ihn für std::unique_ptr, und rufen Sie std::make_unique im Standardkonstruktor auf.

template<typename T> 
class Example<std::unique_ptr<T>> { 
    Example() : data{std::make_unique<T>()} {} 

    /* rest of the class */ 
}; 

Wenn Sie dies tun, sollten Sie nicht Ihre Klasse für T* spezialisieren ein new zu tun, da Sie nicht nicht-besitzenden Zeiger initialisieren kann. Sie sollten es in Ihrem Konstruktor erhalten und möglicherweise den Standardkonstruktor für unformatierte Zeiger deaktivieren, wenn Sie nicht möchten, dass es null ist.

template<typename T> 
class Example<T*> { 
    Example() = delete; 
    Example(T* data_) : data{data_} 

    /* data is not an owning pointer. No need for a destructor */ 

    /* rest of the class */ 
}; 

Wenn Sie diese Regeln befolgen, sollten Sie kein Problem mit der Speicherverwaltung haben.

0

Verwenden Sie Helper-Vorlagenklassen für die Freigabe von Arbeitsspeicher, die nach Typ ausgewählt werden können. Sie müssen Ihre Klasse nicht mit Template-Spezialisierung duplizieren. Sie können nur eine Klasse schreiben.

#include <type_traits> 

template<typename T> // primary template 
struct Releaser 
{ 
    template<typename V> 
    void release(V v) { } 
}; 
template<> // explicit specialization for T = std::true_type 
struct Releaser<std::true_type> 
{ 
    template<typename V> 
    void release(V v) { delete[] v; } 
}; 

template <class T> 
class Example 
{ 
    private: 
    T data; 
    public: 
    Example(): data(T()) {} 
    Example(T typeData): data(typeData) {} 
    typedef typename std::is_pointer<T>::value_type isptr_type; 
    ~Example() { 
     Releaser<isptr_type>::release(data); 
    } 
}; 

Aber brauchen Form von neuen wissen genannt, so verwenden löschen oder [] löschen.

Verwandte Themen