2009-02-12 6 views
12

Sto provando a scrivere un allocatore STL personalizzato derivato da std::allocator, ma in qualche modo tutte le chiamate a allocate() passano alla classe base. Ho ristretto la scelta a questo codice: "Yo"Perché questo allocatore di C++ STL non si assegna?

template <typename T> class a : public std::allocator<T> { 
public: 
    T* allocate(size_t n, const void* hint = 0) const { 
     cout << "yo!"; 
     return 0; 
    } 
}; 

int main() 
{ 
    vector<int, a<int>> v(1000, 42); 
    return 0; 
} 

mi aspetto per essere stampato, seguito da un errore orribile perché in realtà non assegno nulla. Invece, il programma funziona correttamente e non stampa nulla. Che cosa sto facendo di sbagliato?

Ottengo gli stessi risultati in gcc e VS2008.

+0

correlate, vedere [Perché non ereditare da std :: allocator ] (http: // StackOverflow.com/q/21.081.796). – jww

risposta

6

Sarà necessario fornire un modello di membro di rebind e le altre cose elencate nei requisiti di allocatore nello standard C++. Ad esempio, è necessario un costruttore di copia modello che accetta non solo allocator<T> ma anche allocator<U>. Per esempio, un codice potrebbe fare, che uno std :: list per esempio, è probabile che a fare

template<typename Allocator> 
void alloc1chunk(Allocator const& alloc) { 
    typename Allocator::template rebind< 
     wrapper<typename Allocator::value_type> 
     >::other ot(alloc); 
    // ... 
} 

Il codice avrà esito negativo se non ci sia esiste alcun modello Rebind corretta, o non esistono costruttore di copia corrispondente. Non otterrai nulla utile per indovinare quali sono i requisiti. Prima o poi avrai a che fare con il codice che si basa su una parte di questi requisiti di allocatore e il codice non funzionerà perché l'allocatore li viola. Vi consiglio di dare un'occhiata a loro in qualche bozza di lavoro vostra copia della norma in 20.1.5.

+0

Buon punto a proposito della lettura dei requisiti di interfaccia effettivi - altrimenti non saprai mai * di sicuro * che stai gestendo tutto ciò che un allocatore dovrebbe fare. –

1

Il seguente codice stampa "yo" come previsto: quello che stavi vedendo era il nostro vecchio amico "comportamento indefinito".

#include <iostream> 
#include <vector> 
using namespace std; 

template <typename T> class a : public std::allocator<T> { 
public: 
    T* allocate(size_t n, const void* hint = 0) const { 
     cout << "yo!"; 
     return new T[10000]; 
    } 
}; 

int main() 
{ 
    vector<int, a<int> > v(1000, 42); 
    return 0; 
} 

Modifica: ho appena controllato lo standard C++ per quanto riguarda l'allocatore predefinito. Non vi è alcun divieto di ereditare da esso. In effetti, per quanto ne so, non esiste tale divieto in nessuna parte dello Standard.

+0

Questo non ha funzionato per me. VS 2008. –

+0

Hai testato questo codice? Non può funzionare in quanto è praticamente la stessa della domanda. L'ho eseguito anche in modalità debug e release e non funziona. – Klaim

+0

Funziona bene per me con i686-apple-darwin8-g ++ - 4.0.1 –

4

In questo caso, il problema è che non ho eseguito l'override del membro rebind dell'allocatore. Questa versione funziona (in VS2008):

template <typename T> class a : public std::allocator<T> { 
public: 
    T* allocate(size_t n, const void* hint = 0) const { 
     cout << "yo!"; 
     return 0; 
    } 

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

int main() { 
    vector<int, a<int>> v(1000, 42); 
    return 0; 
} 

Ho trovato questo il debug attraverso le intestazioni STL.

Se questo funziona o meno dipenderà completamente dall'implementazione STL, quindi penso che in definitiva, Klaim abbia ragione nel ritenere che ciò non dovrebbe essere fatto in questo modo.

+1

In realtà la distribuzione delle funzioni virtuali non viene utilizzata per le chiamate degli allocatori poiché il tipo è noto al momento della compilazione, quindi credo che ora dovrebbe funzionare sempre. L'unica ragione per non preoccuparsi di ereditare da std :: allocator è perché in realtà non ti risparmia molto la digitazione! –

+0

... ma vedi la risposta di litb per un altro requisito (template copy ctor) che devi implementare. –

2

Ho due modelli per la creazione di allocatori personalizzati; le prime opere automagicamente se viene utilizzato su un tipo personalizzato:

template<> 
class std::allocator<MY_TYPE> 
{ 
public: 
    typedef size_t  size_type; 
    typedef ptrdiff_t difference_type; 
    typedef MY_TYPE* pointer; 
    typedef const MY_TYPE* const_pointer; 
    typedef MY_TYPE& reference; 
    typedef const MY_TYPE& const_reference; 
    typedef MY_TYPE  value_type; 

    template <class U> 
    struct rebind 
    { 
     typedef std::allocator<U> other; 
    }; 

    pointer allocate(size_type n, std::allocator<void>::const_pointer hint = 0) 
    { 
     return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T))); 
    } 
    void construct(pointer p, const_reference val) 
    { 
     ::new(p) T(val); 
    } 
    void destroy(pointer p) 
    { 
     p->~T(); 
    } 
    void deallocate(pointer p, size_type n) 
    { 
     FREE_FUNC(p); 
    } 
    size_type max_size() const throw() 
    { 
     // return ~size_type(0); -- Error, fixed according to Constantin's comment 
     return std::numeric_limits<size_t>::max()/sizeof(MY_TYPE); 
    } 
}; 

Il secondo viene usato quando vogliamo avere il nostro allocatore per un tipo predefinito con un allocatore di serie, per esempio char, wchar_t, std :: stringa, ecc .:

namespace MY_NAMESPACE 
    { 

    template <class T> class allocator; 

    // specialize for void: 
    template <> 
    class allocator<void> 
    { 
    public: 
     typedef void*  pointer; 
     typedef const void* const_pointer; 
     // reference to void members are impossible. 
     typedef void  value_type; 

     template <class U> 
     struct rebind 
     { 
      typedef allocator<U> other; 
     }; 
    }; 

    template <class T> 
    class allocator 
    { 
    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; 

     template <class U> 
     struct rebind 
     { 
      typedef allocator<U> other; 
     }; 

     allocator() throw() 
     { 
     } 
     template <class U> 
     allocator(const allocator<U>& u) throw() 
     { 
     } 
     ~allocator() throw() 
     { 
     } 

     pointer address(reference r) const 
     { 
      return &r; 
     } 
     const_pointer address(const_reference r) const 
     { 
      return &r; 
     } 
     size_type max_size() const throw() 
     { 
      // return ~size_type(0); -- Error, fixed according to Constantin's comment 
      return std::numeric_limits<size_t>::max()/sizeof(T); 
     } 
     pointer allocate(size_type n, allocator<void>::const_pointer hint = 0) 
     { 
      return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T))); 
     } 
     void deallocate(pointer p, size_type n) 
     { 
      FREE_FUNC(p); 
     } 

     void construct(pointer p, const_reference val) 
     { 
      ::new(p) T(val); 
     } 
     void destroy(pointer p) 
     { 
      p->~T(); 
     } 
    }; 

template <class T1, class T2> 
inline 
bool operator==(const allocator<T1>& a1, const allocator<T2>& a2) throw() 
{ 
    return true; 
} 

template <class T1, class T2> 
inline 
bool operator!=(const allocator<T1>& a1, const allocator<T2>& a2) throw() 
{ 
    return false; 
} 

} 

Il primo modello di cui sopra, per il proprio tipo definito, non richiede alcuna ulteriore manipolazione, ma è utilizzato automaticamente dalle classi container standard. Il secondo modello richiede ulteriore lavoro se utilizzato su un tipo standard. Per std :: string, per esempio, si devono utilizzare il seguente costrutto quando dichiara variabili di quel tipo (è più semplice con un typedef):

std::basic_string<char>, std::char_traits<char>, MY_NAMESPACE::allocator<char> > 
+0

Sembra che 'max_size()' dovrebbe restituire 'std :: numeric_limits :: max()/sizeof (MY_TYPE)'. – Constantin

+0

@Constantin: osservazione molto buona, grazie mille! Aggiornerò il codice di conseguenza. :-) –