2008-10-03 14 views
6

Parece como si tuviera un malentendido fundamental sobre C++: <C++ alternativas para anular * punteros (que no es plantillas)

me gusta la solución de contenedores polimórficos. Muchas gracias, por traer a mi atención que :)


Por lo tanto, tenemos la necesidad de crear un objeto de tipo contenedor relativamente genérico. También sucede que encapsula una lógica relacionada con el negocio. Sin embargo, necesitamos almacenar datos esencialmente arbitrarios en este contenedor, desde tipos de datos primitivos hasta clases complejas.

Por lo tanto, uno inmediatamente salta a la idea de una clase de plantilla y se hace con ella. Sin embargo, he notado que el polimorfismo C++ y las plantillas no funcionan bien juntas. Siendo que hay una lógica compleja que vamos a tener que trabajar, preferiría limitarme a las plantillas O al polimorfismo, y no tratar de luchar contra C++ haciendo que haga ambas cosas.

Finalmente, dado que quiero hacer una u otra, preferiría el polimorfismo. Me resulta mucho más fácil representar restricciones como "este contenedor contiene tipos comparables" - a la java.

Acercandome al tema de la pregunta: En términos abstractos, imagino que podría tener una interfaz virtual pura de "Contenedor" que tiene algo parecido a "datos push (void *) y pop (void *)" (para el registro, en realidad no estoy tratando de implementar una pila).

Sin embargo, realmente no me gusta el vacío * en el nivel superior, sin mencionar que la firma va a cambiar cada vez que quiero agregar una restricción al tipo de datos con los que puede trabajar un contenedor concreto.

Resumiendo: Tenemos contenedores relativamente complejos que tienen varias formas de recuperar elementos. Queremos ser capaces de variar las restricciones sobre los elementos que pueden entrar en los contenedores. Los elementos deberían funcionar con múltiples tipos de contenedores (siempre que cumplan con las restricciones de ese contenedor en particular).

Editar: También debo mencionar que los contenedores deben ser polimórficos. Esa es mi razón principal para no querer utilizar C++ con plantilla.

Entonces, ¿debería abandonar mi amor por las interfaces tipo Java e ir con plantillas? ¿Debería usar void * y lanzar todo de forma estática? ¿O debería ir con una definición de clase vacía "Elemento" que no declara nada y usar eso como mi clase de nivel superior en la jerarquía "Elemento"?

Una de las razones por las que amo el desbordamiento de la pila es que muchas de las respuestas brindan una perspectiva interesante sobre otros enfoques que ni siquiera había considerado. Así que gracias de antemano por sus ideas y comentarios.

+0

¿qué quiere decir "polimorfismo y plantillas no funcionan bien juntas"? –

+0

Específicamente, un contenedor polimórfico: olvidé mencionar ese requisito. Hasta donde yo sé, no se puede hacer ... pero entonces, ¿qué sé? – Voltaire

+0

> Tengo una necesidad de que los contenedores sean polimórficos. Actualicé mi respuesta en consecuencia. – Lev

Respuesta

3

¿Puede usted no tiene una clase de contenedor de raíz que contiene elementos:

template <typename T> 
class Container 
{ 
public: 

    // You'll likely want to use shared_ptr<T> instead. 
    virtual void push(T *element) = 0; 
    virtual T *pop() = 0; 
    virtual void InvokeSomeMethodOnAllItems() = 0; 
}; 

template <typename T> 
class List : public Container<T> 
{ 
    iterator begin(); 
    iterator end(); 
public: 
    virtual void push(T *element) {...} 
    virtual T* pop() { ... } 
    virtual void InvokeSomeMethodOnAllItems() 
    { 
     for(iterator currItem = begin(); currItem != end(); ++currItem) 
     { 
      T* item = *currItem; 
      item->SomeMethod(); 
     } 
    } 
}; 

Estos contenedores pueden ser pasados ​​a través polimórfica:

class Item 
{ 
public: 
    virtual void SomeMethod() = 0; 
}; 

class ConcreteItem 
{ 
public: 
    virtual void SomeMethod() 
    { 
     // Do something 
    } 
}; 

void AddItemToContainer(Container<Item> &container, Item *item) 
{ 
    container.push(item); 
} 

... 

List<Item> listInstance; 
AddItemToContainer(listInstance, new ConcreteItem()); 
listInstance.InvokeSomeMethodOnAllItems(); 

Esto le da la interfaz de contenedores en un tipo de fallos forma genérica

Si desea añadir restricciones al tipo de elementos que puede contener, se puede hacer algo como esto:

class Item 
{ 
public: 
    virtual void SomeMethod() = 0; 
    typedef int CanBeContainedInList; 
}; 

template <typename T> 
class List : public Container<T> 
{ 
    typedef typename T::CanBeContainedInList ListGuard; 
    // ... as before 
}; 
+0

Eso es lo que estaba pensando. El único problema es que el artículo es esencialmente una clase vacía. Por lo que puedo decir, no puedo poner restricciones sobre lo que específicamente está almacenado en el contenedor, por lo que realmente no hay una interfaz común que pueda extraer. Pero quizás eso no sea tan malo. – Voltaire

+0

Además ... No creo que pueda tener funciones virtuales en la plantilla. Otro problema importante;) – Voltaire

+0

Puede tener funciones virtuales en una plantilla. – Lev

5

El polimorfismo y las plantillas funcionan muy bien juntas, si las usa correctamente.

De todos modos, entiendo que desea almacenar solo un tipo de objetos en cada instancia de contenedor. Si es así, usa plantillas. Esto evitará que guarde el tipo de objeto incorrecto por error.

En cuanto a las interfaces del contenedor: Dependiendo de su diseño, tal vez también pueda hacerlas con plantilla, y entonces tendrán métodos como void push(T* new_element). Piense en lo que sabrá sobre el objeto cuando quiera agregarlo a un contenedor (de un tipo desconocido). ¿De dónde vendrá el objeto en primer lugar? ¿Una función que devuelve void*? ¿Sabes que será comparable?Al menos, si todas las clases de objetos almacenados están definidas en su código, puede hacer que todas hereden de un ancestro común, por ejemplo, Storable, y use Storable* en lugar de void*.

Ahora si ve que los objetos siempre se agregarán a un contenedor por un método como void push(Storable* new_element), entonces realmente no habrá ningún valor agregado para hacer que el contenedor sea una plantilla. Pero entonces sabrá que debería almacenar Storable.

+0

Aunque esto podría estar mal, creo que es seguro asumir que sí, que cada contenedor almacenará un elemento de un tipo. Sin embargo, tengo la necesidad de que los contenedores sean polimórficos. Eso es lo que encuentro difícil con las plantillas. – Voltaire

+0

"si los usa correctamente" = caída de muchas supuestas buenas ideas. – DarenW

5

Lo simple es definir una clase base abstracta llamada Container, y la subclase para cada tipo de elemento que desee almacenar. Luego puede usar cualquier clase de colección estándar (std::vector, std::list, etc.) para almacenar punteros al Container. Tenga en cuenta que, dado que estaría almacenando punteros, tendría que manejar su asignación/desasignación.

Sin embargo, el hecho de que necesite una sola colección para almacenar objetos de tipos tan diferentes es una indicación de que algo puede estar mal con el diseño de su aplicación. Puede ser mejor revisar la lógica de negocio antes de implementar este contenedor súper genérico.

13

Puede ver usando un contenedor estándar de boost::any si está almacenando datos verdaderamente arbitrarios en el contenedor.

suena más como se quiere tener algo así como un boost::ptr_container donde cualquier cosa que puede ser almacenado en el contenedor tiene que derivar de algún tipo de base, y el propio contenedor sólo se le puede dar la referencia de que el tipo de base.

1

Al usar polimorfismo, básicamente se queda con una clase base para el contenedor y clases derivadas para los tipos de datos. La clase base/clases derivadas pueden tener tantas funciones virtuales como necesite, en ambas direcciones.

Por supuesto, esto significa que también deberá envolver los tipos de datos primitivos en clases derivadas. Si reconsideras el uso general de las plantillas, aquí es donde usaría las plantillas. Cree una clase derivada a partir de la base, que es una plantilla, y utilícela para los tipos de datos primitivos (y para otros en los que no necesita más funcionalidad que la que proporciona la plantilla).

No olvide que puede hacer su vida más fácil con typedefs para cada uno de los tipos de plantilla, especialmente si más tarde necesita convertir uno de ellos en una clase.

3

En primer lugar, las plantillas y el polimorfismo son conceptos ortogonales y funcionan bien juntos. A continuación, ¿por qué quieres una estructura de datos específica? ¿Qué pasa con las estructuras de datos STL o impulsar (específicamente pointer containter) no funciona para usted.

Dada su pregunta, parece que estaría mal uso de la herencia en su situación. Es posible crear "constraints" en lo que va en sus contenedores, especialmente si está usando plantillas. Esas limitaciones pueden ir más allá de lo que tu compilador y vinculador te darán. De hecho, es más incómodo para ese tipo de cosas con herencia y es más probable que los errores queden para el tiempo de ejecución.

+1

El contenedor en sí tiene propiedades interesantes, independientes de los datos que almacena. Y sería bueno si fuera polimórfico. Puede haber una biblioteca de código abierto que esté cerca de lo que necesitamos, pero dudo que se ajuste exactamente a nuestros requisitos. – Voltaire

1

Es posible que también desee comprobar hacia fuera The Boost Concept Check Library (BCCL) que está diseñado para proporcionar restricciones en el parámetros de plantilla de clases con plantilla, sus contenedores en este caso.

Y para reiterar lo que otros han dicho, nunca he tenido problemas para mezclar el polimorfismo y las plantillas, y he hecho algunas cosas bastante complejas con ellos.

0

No podría tener que renunciar a las interfaces tipo Java y usar plantillas también. Josh's suggestion de una plantilla base genérica. El contenedor ciertamente le permitiría pasar contenedores polimórficamente y sus hijos, pero también podría implementar interfaces como clases abstractas para que sean los elementos contenidos. No hay ninguna razón por la que no se podía crear una clase IComparable abstracta como usted sugiere, de manera que usted podría tener una función polimórfica de la siguiente manera:

class Whatever 
{ 
    void MyPolymorphicMethod(Container<IComparable*> &listOfComparables); 
} 

Este método puede ahora tomar cualquier niño de los recipientes que contenga cualquier clase que implementa IComparable, por lo que sería extremadamente flexible.

Cuestiones relacionadas