2012-01-04 15 views
5

¿Cuál es la forma más elegante de proporcionar una interfaz en C++ que acepta tipos de clases derivadas que llevan consigo diferentes tipos de datos para luego recuperarlos? El siguiente ejemplo ilustra esto donde la clase Container proporciona métodos para "publicar" un elemento que será algún tipo de variante derivada de BaseItem. Más tarde quiero recuperar el artículo derivado y extraer su valor.La mejor manera para que las clases derivadas transporten diferentes tipos de datos en C++

Lo principal que quiero es que la interfaz del contenedor (publicar y recibir) permanezca igual en el futuro al mismo tiempo que permite que diferentes tipos derivados de "elementos" se definan y "pasen" a través de él. La plantilla sería mejor para esto de alguna manera; Prefiero no usar RTTI. Tal vez haya alguna respuesta simple y elegante a esto, pero ahora estoy luchando por pensarlo.

class ItemBase { 
    // common methods 
}; 

class ItemInt : public ItemBase 
{ 
    private: 
    int dat; 
    public: 
    int get() { return dat; } 
}; 

class ItemDouble : public ItemBase 
{ 
    private: 
    double dat; 
    public: 
    double get() { return dat; } 
}; 

class Container { 
public: 
    void post(int postHandle, ItemBase *e);  
    ItemBase* receive(int handle); // Returns the associated Item 
}; 

int main() 
{ 
    ItemInt *ii = new IntItem(5); 
    Container c; 
    c.post(1, ii); 

    ItemInt *jj = c.receive(1); 
    int val = jj->get(); // want the value 5 out of the IntItem 
} 
+1

Pruebe Boost.any, que hace algo muy similar. –

+0

También tenga en cuenta que el código publicado tiene el problema del 'get' de cada objeto con un tipo de devolución diferente. Si no puede hacer que sean del mismo tipo, tendrá que devolver un stringstream o algo así. –

+0

Sí, estoy de acuerdo, pero la función get() no es una función virtual. Es "funcionalidad separada" que cada clase derivada "agrega". Hay otra funcionalidad común que hace que la herencia sea "necesaria" –

Respuesta

5

Esto es definitivamente un candidato para la programación genérica, en lugar de la herencia. Recuerde, los genéricos (plantillas) son ideales cuando desea un manejo idéntico para diferentes tipos de datos. Las clases ItemInt y ItemDouble infringen los principios de diseño de OO (el método get() devuelve diferentes tipos de datos según el subtipo real). La programación genérica está construida para eso. La única otra respuesta sería un tipo de datos etiquetados, y personalmente evito aquellos como la plaga.

+0

Acepto que podríamos crear una plantilla Item y obtener get() return T. Pero, ¿qué tipo sería el segundo argumento para publicar()? Quiero que acepte Elementos con cualquier parámetro de plantilla, T. –

+0

@innocent_bystander: Generalmente, haría 'Container :: post' y' Container :: recieve' ambas plantillas, y saltaría el 'ItemBase' de altogeather. –

0

Su clase de contenedor parece sospechosamente como un estándar :: mapa. Me parece que su clase ItemBase es simplemente un nombre diferente para "Object", la clase base universal, que creo que no es muy diferente de (o mejor que) void *. Evitaría tratar de contener elementos de diferente tipo en un solo contenedor. Si su diseño parece requerirlo, reconsideraría su diseño.

0

¿Qué tal?

template<typename T> 
class Item 
{ 
    private: 
    T dat; 
    public: 
    T get() { return dat; } 
}; 

class Container { 
public: 
    template<typename T> 
    void post(int postHandle, Item<T> *e);  

    template<typename T> 
    Item<T>* receive(int handle); // Returns the associated Item 
}; 

int main() 
{ 
    Item<int> *ii = new Item<int>(5); 
    Container c; 
    c.post(1, ii); 

    Item<int> *jj = c.receive<int>(1); 
    int val = jj->get(); // want the value 5 out of the IntItem 
} 
0

Un enfoque de plantilla pura no funciona porque aparentemente desea tener tipos mixtos en su contenedor. Podría trabajar con algo como el any de Boost, aunque creo que debe restaurar el real. Lo que creo que se llama en este caso es una clase base exponer los métodos de tipo-independientes y virtuales, además de una clase derivada a plantillas para contener los elementos reales:

class Base { 
public: 
    virtual ~Base() {} 
    virtual void post() = 0; 
}; 

template <typename T> 
class Item: public Base { 
public: 
    Item(T const& value): value_(value) {} 
    void post() { std::cout << "posting " << this->value_ << "\n"; } 
private: 
    T value_; 
}; 

Este enfoque evita la necesidad de escribir cualquier deriva Item clase para otro tipo de valor. Para facilitar la creación de estas bestias, probablemente también desee crear una función de creación adecuada, p.

template <typename T> 
std::unique_ptr<Base> make_item(T const& value) { 
    return std::unique_ptr<Base>(new Item<T>(value)); 
} 

Un std::unique_ptr<Base> se devuelve para asegurarse de que el objeto se libera asignado (si no se utiliza C++ 2011 puede std::auto_ptr<T> utilizado en su lugar). Este tipo se puede convertir fácilmente a otros tipos de punteros, p. a un std::shared_ptr<Base> que es más adecuado para ponerlo en un contenedor.

Cuestiones relacionadas