2010-09-10 9 views
6

Esta es una secuela de un related post la que hizo la pregunta eterna:¿Puedo tener contenedores polimórficos con semántica de valores en C++ 11?

¿Puedo tener contenedores polimórficos con la semántica de valor en C++?

La pregunta se hizo de forma ligeramente incorrecta. Debería haber sido más como:

¿Puedo tener contenedores STL de un tipo básico almacenados por valor en el que los elementos muestran un comportamiento polimórfico?

Si realiza la pregunta en términos de C++, la respuesta es "no". En algún punto, cortará los objetos almacenados por valor.

Ahora hago la pregunta nuevamente, pero estrictamente en términos de C++ 11. Con los cambios en el lenguaje y las bibliotecas estándar, ¿ahora es posible almacenar objetos polimórficos por valor en un contenedor STL?

Soy muy consciente de la posibilidad de almacenar un puntero inteligente en la clase base en el contenedor - esto no es lo que estoy buscando, ya que estoy tratando de construir objetos en la pila sin usando new.

cuenta si se quiere (del poste vinculados) tan básico C++ ejemplo:

#include <iostream> 

using namespace std; 

class Parent 
{ 
    public: 
     Parent() : parent_mem(1) {} 
     virtual void write() { cout << "Parent: " << parent_mem << endl; } 
     int parent_mem; 
}; 

class Child : public Parent 
{ 
    public: 
     Child() : child_mem(2) { parent_mem = 2; } 
     void write() { cout << "Child: " << parent_mem << ", " << child_mem << endl; } 

     int child_mem; 
}; 

int main(int, char**) 
{ 
    // I can have a polymorphic container with pointer semantics 
    vector<Parent*> pointerVec; 

    pointerVec.push_back(new Parent()); 
    pointerVec.push_back(new Child()); 

    pointerVec[0]->write();  
    pointerVec[1]->write();  

    // Output: 
    // 
    // Parent: 1 
    // Child: 2, 2 

    // But I can't do it with value semantics 

    vector<Parent> valueVec; 

    valueVec.push_back(Parent()); 
    valueVec.push_back(Child());  // gets turned into a Parent object :(

    valueVec[0].write();   
    valueVec[1].write();   

    // Output: 
    // 
    // Parent: 1 
    // Parent: 2 

} 
+2

boost: ptr_vector debería hacer lo que quiera. –

+1

Tenga en cuenta que puede simular la semántica de valores (al menos en términos de contenedores STL) con algo como 'boost :: ptr_container'. –

+0

@Martin: ¿cómo te atreves a vencerme por 15 segundos?!? –

Respuesta

4

Solo por diversión, basado en el comentario de James sobre un sistema basado en plantillas, se me ocurrió esta implementación tipo Vector. Le faltan muchas características y puede tener errores, ¡pero es un comienzo!

#include <iostream> 
#include <vector> 
#include <boost/shared_ptr.hpp> 

template <typename T> 
class Vector 
{ 
public: 
    T &operator[] (int i) const { return p[i]->get(); } 
    template <typename D> 
    void push_back(D &x) { p.push_back(ptr_t(new DerivedNode<D>(x))); } 

private: 
    class Node 
    { 
    public: 
     virtual T &get() = 0; 
    }; 

    template <typename D> 
    class DerivedNode : public Node 
    { 
    public: 
     DerivedNode(D &x) : x(x) {} 
     virtual D &get() { return x; } 
    private: 
     D x; 
    }; 

    typedef boost::shared_ptr<Node> ptr_t; 
    std::vector<ptr_t> p; 
}; 

/////////////////////////////////////// 

class Parent 
{ 
    public: 
     Parent() : parent_mem(1) {} 
     virtual void write() const { std::cout << "Parent: " << parent_mem << std::endl; } 
     int parent_mem; 
}; 

class Child : public Parent 
{ 
    public: 
     Child() : child_mem(2) { parent_mem = 2; } 
     void write() const { std::cout << "Child: " << parent_mem << ", " << child_mem << std::endl; } 

     int child_mem; 
}; 


int main() 
{ 
    Vector<Parent> v; 

    v.push_back(Parent()); 
    v.push_back(Child()); 

    v[0].write(); 
    v[1].write(); 
} 
+0

Esto está cerca de las bibliotecas de Adobe Source ' 'clases. ¡Bonito! – fbrereto

+2

está reinventando la rueda: 'boost :: ptr_vector' lo hace mejor (no' shared_ptr' involucrado). También tenga en cuenta que el requisito básico no era 'nuevo'. Con un 'nuevo' es trivial (y hay bibliotecas). –

8

cierto, no puede tener una variedad polimórfica (o vector). El requisito de que los elementos de una matriz se almacenen contiguamente en la memoria es fundamentalmente incompatible con el hecho de que diferentes tipos de clases derivadas pueden tener diferentes tamaños.

Ninguno de los contenedores de biblioteca estándar permite almacenar objetos de diferentes tipos de clases derivadas en un único contenedor.

+0

Incluso con, por ejemplo, 'list', el problema de tamaño aún existe: ¿qué tan grande debe ser un nodo? –

+1

@Oli: Bueno, mi primer pensamiento con eso fue que podrías tener una plantilla de clase de nodo derivada que se puede instanciar con objetos de diferentes tamaños, lo que requeriría al menos (a) una forma estándar para determinar el tamaño del tipo dinámico de un objeto y (b) una forma estándar de clonar un objeto. Incluso entonces, sin embargo, creo que habría problemas para que el despacho dinámico funcione correctamente. No creo que un contenedor así funcione mejor que un contenedor de punteros, tampoco. En el mejor de los casos sería desordenado; es posible que no sea posible en absoluto. Sin embargo, sería un proyecto divertido para jugar. –

+0

ver mi respuesta! Tomé tu idea y corrí con ella ... comentarios bienvenidos. –

2

Antes que nada, sus requisitos aún no están perfectamente claros. Supongo que quiere "almacenamiento en línea" para el contenedor; por lo tanto, por ejemplo, en un "polimórfico" vector, todos los elementos estarían adyacentes en la memoria (con solo relleno entre ellos según sea necesario para la alineación correcta).

Ahora, es posible si está dispuesto a proporcionar una lista exhaustiva de todos los tipos que va a colocar en el contenedor en tiempo de compilación. La implementación más directa sería usar una unión de todos los tipos posibles como el tipo de la matriz de respaldo, que aseguraría el tamaño suficiente y la alineación adecuada, y el mismo acceso O (1) por índice, a costa de un espacio desperdiciado en los elementos de tipos de menor tamaño. Puedo entrar en esto con más detalles si quieres.

Si la lista de tipos se conoce ahora de antemano, o si no desea ese tipo de sobrecarga, entonces debe mantener un índice de punteros separados (o desplazamientos desde el principio de la tienda de respaldo) para elementos, para que pueda hacer O (1) acceso. Además, dados los problemas de alineación, no estoy seguro de si podrías hacer eso en C++ 03 totalmente portátil, aunque definitivamente puedes hacerlo en C++ 0x.

+0

Si sus tipos tienen constructores de copias, etc., no puede ponerlos en una unión. –

+2

Es cierto, aunque siempre se puede usar el mismo truco que utiliza 'boost :: variant' (o, para el caso, simplemente usarlo directamente). –

+1

+1: 'std :: vector >' es la mejor respuesta que podría encontrar también. Tenga en cuenta que los visitantes de esta estructura solo necesitan 'return_type operator() (Base const &)' ya que los demás son convertibles a 'Base'. –

Cuestiones relacionadas