2014-01-12 9 views
13

ich meine eigene allocator wie so erstellt:Warum nicht von std :: erben allocator

template<typename T> 
class BasicAllocator 
{ 
    public: 
     typedef size_t size_type; 
     typedef ptrdiff_t difference_type; 
     typedef T* pointer; 
     typedef const T* const_pointer; 
     typedef T& reference; 
     typedef const T& const_reference; 
     typedef T value_type; 


     BasicAllocator() throw() {}; 
     BasicAllocator (const BasicAllocator& other) throw() {}; 

     template<typename U> 
     BasicAllocator (const BasicAllocator<U>& other) throw() {}; 

     template<typename U> 
     BasicAllocator& operator = (const BasicAllocator<U>& other) {return *this;} 
     BasicAllocator<T>& operator = (const BasicAllocator& other) {return *this;} 
     ~BasicAllocator() {} 

     pointer address (reference value) const {return &value;} 
     const_pointer address (const_reference value) const {return &value;} 

     pointer allocate (size_type n, const void* hint = 0) {return static_cast<pointer> (::operator new (n * sizeof (value_type)));} 
     void deallocate (void* ptr, size_type n) {::operator delete (static_cast<T*> (ptr));} 

     template<typename U, typename... Args> 
     void construct (U* ptr, Args&& ... args) {::new (static_cast<void*> (ptr)) U (std::forward<Args> (args)...);} 
     void construct (pointer ptr, const T& val) {new (static_cast<T*> (ptr)) T (val);} 

     template<typename U> 
     void destroy (U* ptr) {ptr->~U();} 
     void destroy (pointer ptr) {ptr->~T();} 

     size_type max_size() const {return std::numeric_limits<std::size_t>::max()/sizeof (T);} /**return std::size_t(-1);**/ 

     template<typename U> 
     struct rebind 
     { 
      typedef BasicAllocator<U> other; 
     }; 
}; 

Aber ich möchte wissen, warum ich nie von std::allocator erben sollte. Liegt es daran, dass es keinen virtuellen Destruktor gibt? Ich habe viele Beiträge gesehen, die sagen, dass man eigene erstellen und nicht erben sollte. Ich verstehe, warum wir nicht std::string und std::vector erben sollten, aber was ist falsch mit std::allocator erben?

Kann ich meine Klasse oben erben? Oder brauche ich dazu einen virtuellen Destruktor?

Warum?

+0

' new (static_cast (ptr)) 'sieht aus wie ein Tippfehler (die Funktion nimmt bereits einen' Zeiger == T * ') – dyp

Antwort

25

Eine Menge Leute in diesem Thread posten gehen, dass Sie nicht von std::allocator erben sollte, weil es kein virtuelles destructor hat. Sie sprechen über Polymorphie und schneiden und löschen über eine Klasse von Zeiger zu Basis, von denen keine von den Zuweisungsanforderungen zugelassen ist, wie in Abschnitt 17.6.3.5 [allocator.requirements] des Standards beschrieben. Bis jemand zeigt, dass eine von std::allocator abgeleitete Klasse eine dieser Anforderungen nicht erfüllt, ist es eine einfache Cargo- Kult-Mentalität.

Das heißt, es gibt wenig Grund, von std::allocator in C++ 11 abzuleiten. In C++ 11 wurde die Zuteilungsvorlage std::allocator_traits zwischen einem Zuordner und seinen Benutzern neu erstellt und bietet über Template-Metaprogrammierung angemessene Standardwerte für viele der erforderlichen Features.A minimal allocator in C++11 can be as simple as:

template <typename T> 
struct mallocator { 
    using value_type = T; 

    mallocator() = default; 
    template <class U> 
    mallocator(const mallocator<U>&) {} 

    T* allocate(std::size_t n) { 
    std::cout << "allocate(" << n << ") = "; 
    if (n <= std::numeric_limits<std::size_t>::max()/sizeof(T)) { 
     if (auto ptr = std::malloc(n * sizeof(T))) { 
     return static_cast<T*>(ptr); 
     } 
    } 
    throw std::bad_alloc(); 
    } 
    void deallocate(T* ptr, std::size_t n) { 
    std::free(ptr); 
    } 
}; 

template <typename T, typename U> 
inline bool operator == (const mallocator<T>&, const mallocator<U>&) { 
    return true; 
} 

template <typename T, typename U> 
inline bool operator != (const mallocator<T>& a, const mallocator<U>& b) { 
    return !(a == b); 
} 

EDIT: Der richtige Einsatz von std::allocator_traits ist in allen Standardbibliotheken noch nicht vollständig vorhanden. Beispielsweise funktioniert der obige Beispielzuordner nicht ordnungsgemäß mit std::list, wenn mit GCC 4.8.1 kompiliert wird - der std::list-Code beschwert sich über fehlende Mitglieder, da es noch nicht aktualisiert wurde.

+0

Ich mag das! Ich wusste nicht, dass ich das tun kann, was Sie dort haben, ohne jedes Mitglied, das ich in meinem Allokator habe, explizit zu erstellen. Danke dafür! Ich dachte, ich müsste die allocator_traits implementieren. Ich wusste nicht, dass die Container es benutzten. =) Genau das wollte ich wissen. – Brandon

+0

Für Ihre Informationen, versucht std :: list > auf Claming mit libC++ und Clang eigenen C++ abi und kompiliert fein. – user534498

+0

@ user534498 libC++ war nie dazu gedacht, Sprachversionen früher als C++ 11 zu unterstützen, daher bin ich nicht überrascht, dass es weiter in der allocator_traits-Unterstützung als libstdC++ ist. Danke für die Bestätigung. – Casey

0

Nun, der Destruktor is not virtual. Dies ist kein direktes Problem, wenn Sie den Zuordner nicht polymorph verwenden. Aber betrachten Sie diesen Fall, wo BasicAllocator erbt von std::allocator:

std::allocator<int>* ptr = new BasicAllocator<int>(); 
// ... 
delete ptr; 

Der Destruktor von BasicAllocator ist nie, was zu einem Speicherleck genannt.

+4

Nein, wo in der Standard-Detaillierung von Zuweisungsanforderungen heißt, dass ein Zuweiser unterstützen muss Löschen durch einen Zeiger auf eine seiner Basisklassen. Sie stellen einen guten Grund dar, eine Klasse, die von einer anderen Klasse ohne einen virtuellen Destruktor abgeleitet wurde, im Allgemeinen nicht polymorph zu behandeln, aber sie beantwortet die Frage des OP nicht, da sie nicht auf den speziellen Fall von Allokatoren zutrifft. – Casey

8

Die Klassenvorlage std::allocator<...> hat keine virtuellen Funktionen. Somit ist es eindeutig ein schlechter Kandidat, abgeleitete Funktionalität bereitzustellen. Während einige Klassen oder Klassenvorlagen immer noch vernünftige Basisklassen sind, sind diese auch ohne virtuellen Destruktor und jede andere virtuelle Funktion entweder nur Tag-Typen oder verwenden die Curiously recurring template pattern.

Zuweiser sind nicht dazu gedacht, so angepasst zu werden, d.h. std::allocator<T> ist nicht als Basisklasse gedacht. Wenn Sie versuchen, es als solches zu verwenden, kann Ihre Logik leicht abgeschnitten werden. Der Ansatz, der für die einfache Anpassung von Zuordnern verwendet wird, besteht darin, std::allocator_traits<A> zu verwenden, um die verschiedenen Operationen bereitzustellen, die Ihr Zuordner nicht explizit mithilfe einer Standardimplementierung basierend auf einer relativ kleinen Anzahl von Vorgängen bereitstellen möchte. Das Hauptproblem beim Ableiten von std::allocator<T> ist, dass es ein Problem mit dem rebind-Element verbergen kann, z. B. das Element, das weggelassen oder falsch geschrieben wurde. Unten ist ein Beispiel, das my_allocator::allocate() zweimal drucken sollte, aber nicht aufgrund eines Tippfehlers. Ich denke, my_allocator<T> ist mit Ausnahme des Tippfehlers ein vollständiger Allokator auch ohne die Vererbung von std::allocator<T>, d. H. Die unnötige Vererbung trägt nur zu der Möglichkeit bei, Fehler zu verstecken. Sie können auch einen Fehler erhalten, indem Sie beispielsweise die Funktion allocate() oder deallocate() falsch einstellen.

#include <memory> 
#include <iostream> 

template <typename T> 
struct my_allocator 
    : std::allocator<T> 
{ 
    my_allocator() {} 
    template <typename U> my_allocator(my_allocator<U> const&) {} 

    typedef T value_type; 
    template <typename U> struct rebimd { typedef my_allocator<U> other; }; 
    T* allocate(size_t n) { 
     std::cout << "my_allocator::allocate()\n"; 
     return static_cast<T*>(operator new(n*sizeof(T))); 
    } 
    void deallocate(T* p, size_t) { operator delete(p); } 
}; 

template <typename A> 
void f(A a) 
{ 
    typedef std::allocator_traits<A> traits; 
    typedef typename traits::value_type value_type; 
    typedef typename traits::pointer pointer; 
    pointer p = traits::allocate(a, sizeof(value_type)); 
    traits::deallocate(a, p, sizeof(value_type)); 

    typedef typename traits::template rebind_alloc<int> other; 
    typedef std::allocator_traits<other> otraits; 
    typedef typename otraits::value_type ovalue_type; 
    typedef typename otraits::pointer opointer; 
    other o(a); 
    opointer op = otraits::allocate(o, sizeof(ovalue_type)); 
    otraits::deallocate(o, op, sizeof(ovalue_type)); 
} 

int main() 
{ 
    f(my_allocator<int>()); 
} 
+0

Ich kann keine Beispiele von 'std :: allocator_traits' finden, die verwendet werden oder wofür es ist. Ich habe auch bemerkt, dass Bibliotheken einen Zuordner verwenden, der meinem ähnlich ist und von ihm ohne einen virtuellen Destruktor erbt. Ich habe irgendwo gelesen, dass ein Zuweiser keine virtuellen Funktionen haben sollte. http://gcc.gnu.org/ml/libstdc++/2011-05/msg00120.html – Brandon

+1

Können Sie demonstrieren, welche der Operationen in den Zuweisungsanforderungen (C++ 11 17.6.3.5) das Potenzial hat, Logik zu trennen ? – Casey

+0

@Casey: sicher. Der offensichtliche Schuldige ist das Auslassen oder das Rechtschreiben von "Rebind". Ich habe die Antwort aktualisiert. –

0

Ich habe gerade ein Problem in VS2013 (aber es erscheint nicht in VS2015) darüber. Wahrscheinlich keine Antwort auf diese Frage, aber ich werde es trotzdem teilen:

Im boost gibt es eine Funktion call_select_on_container_copy_construction() testen, ob ein Zuweiser hat ein Mitglied select_on_container_copy_construction() und rufen Sie diese Funktion, um eine Kopie des Zuordners zu bekommen. Während das std::allocator eine Kopie von sich selbst zurückgibt, sollte das abgeleitete myallocator diese Methode überschreiben, um dasselbe zu tun, und den myallocator-Typ zurückgeben, anstatt es dem mit std::allocator Rückgabetyp zu erben. Dies führt zu einem Kompilierungsfehler bei nicht übereinstimmenden Typen.

Wenn myallocatorstd::allocator erbt, muss es jede übergeordnete Methode überschreiben, die beim Überschreiben möglicherweise nicht den gleichen Rückgabetyp mit dem Typ aufweist.

Hinweis, dies erscheint nur in VS2013, soweit ich sehe, so können Sie argumentieren, dass es ein Problem mit dem Compiler ist nicht der Code.

Die verwendete myallocator ist aligned_allocator in Eigen seit Version 3.3.0.

`
Verwandte Themen