2009-02-12 16 views
12

Estoy tratando de escribir un asignador STL personalizado que se deriva de std::allocator, pero de alguna manera todas las llamadas a allocate() van a la clase base. Lo he reducido a este código:¿Por qué no asigna este asignador C++ STL?

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

Espero "Yo!" para que se imprima, seguido de un error horrible porque en realidad no asigno nada. En cambio, el programa funciona bien y no imprime nada. ¿Qué estoy haciendo mal?

Obtengo los mismos resultados en gcc y VS2008.

+0

asociado, véase [¿Por qué no heredar de std :: asignador ] (http: // stackoverflow.com/q/21081796). – jww

Respuesta

6

Deberá proporcionar una plantilla de miembro de reenlace y las demás cosas que figuran en los requisitos del asignador en el Estándar C++. Por ejemplo, necesita un constructor de copia de plantilla que acepte no solo allocator<T> sino también allocator<U>. Por ejemplo, un código podría hacer, que es probable que lo haga

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

El código fallará si allí tampoco existe ninguna plantilla revinculación correcta, o no existen correspondiente constructor de copia un std :: lista por ejemplo. No te resultará útil adivinar cuáles son los requisitos. Tarde o temprano tendrá que ver con un código que depende de una parte de los requisitos del asignador, y el código fallará porque su asignador los viola. Le recomiendo que los eche un vistazo en algunos borradores de trabajo de su copia del Estándar en 20.1.5.

+0

Un buen punto para leer los requisitos reales de la interfaz; de lo contrario, nunca sabrá * con certeza * que está manejando todo lo que se supone que debe hacer un asignador. –

1

El siguiente código imprime "yo" como se esperaba - lo que veías era nuestro viejo amigo "comportamiento indefinido".

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

Editar: Acabo de comprobar el estándar de C++ con respecto al asignador predeterminado. No hay prohibición de heredar de él. De hecho, hasta donde yo sé, no existe tal prohibición en ninguna parte del Estándar.

+0

Esto no funcionó para mí. VS 2008. –

+0

¿Has probado este código? No puede funcionar ya que es básicamente lo mismo que la pregunta. Simplemente lo ejecuto también en modo de depuración y liberación y no funciona. – Klaim

+0

Funciona bien para mí con i686-apple-darwin8-g ++ - 4.0.1 –

4

En este caso, el problema es que no anulé el miembro de reenlace del asignador. Esta versión funciona (en 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; 
} 

que encontramos por la depuración a través de las cabeceras de STL.

Si esto funciona o no dependerá por completo de la implementación de STL, creo que, en última instancia, Klaim tiene razón al decir que esto no debería hacerse de esta manera.

+1

En realidad, el despacho virtual de funciones no se usa para las llamadas al asignador ya que el tipo se conoce en tiempo de compilación, por lo que creo que esto debería funcionar siempre. ¡La única razón para no molestar heredar de std :: allocator es porque realmente no te ahorra mucho tipeo! –

+0

... pero vea la respuesta de litb para otro requisito (copia de plantilla) que debe implementar. –

2

Tengo dos plantillas para crear asignadores personalizados; los primeros trabajos automágicamente si se utiliza en un tipo personalizado:

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

El segundo se utiliza cuando queremos tener nuestro propio asignador de un tipo predefinido con un asignador estándar, por ejemplo carbón, wchar_t, std :: cuerda, etc .:

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

} 

La primera plantilla de encima, para su propio tipo definido, no requiere ninguna manipulación adicional, pero se utiliza automáticamente por las clases de contenedores de transporte. La segunda plantilla requiere más trabajo cuando se usa en un tipo estándar. Para std :: string, por ejemplo, uno tiene que usar el siguiente constructor al declarar variables de ese tipo (que es más simple con un typedef):

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

Parece que 'max_size()' debe devolver 'std :: numeric_limits :: max()/sizeof (MY_TYPE)'. – Constantin

+0

@Constantin: Muy buena observación, ¡muchas gracias! Actualizaré el código en consecuencia. :-) –

Cuestiones relacionadas