2009-10-27 23 views
9

Estoy tratando de enseñarme a mí mismo C++, y uno de los ejercicios tradicionales de "nuevo lenguaje" que siempre he usado es implementar alguna estructura de datos, como un árbol binario o una lista vinculada. En Java, esto era relativamente simple: podía definir algún Nodo de clase que mantuviera una variable de instancia Object data, para que alguien pudiera almacenar cualquier tipo de objeto en cada nodo de la lista o árbol. (Más tarde trabajé modificando esto usando genéricos; de eso no se trata esta pregunta).¿Mantiene la referencia a cualquier tipo de objeto en C++?

No puedo encontrar una manera C++ idiomática similar de almacenar "cualquier tipo de objeto". En C usaría un puntero void; lo mismo funciona para C++, obviamente, pero luego me encuentro con problemas cuando construyo una instancia de std::string y trato de almacenarla en la lista/árbol (algo sobre un molde no válido de std::string& a). ¿Hay tal manera? ¿C++ tiene un equivalente al objeto de Java (o NSObject de Objective-C)?

Pregunta de bonificación: Si no es así, y necesito seguir utilizando punteros vacíos, ¿cuál es la forma "correcta" de almacenar un std::string en un void*? Me encontré con static_cast<char*>(str.c_str()), pero parece un poco detallado para lo que estoy tratando de hacer. ¿Hay una mejor manera?

+3

¿Por qué bajó mi pregunta? –

+0

Estoy interesado en saberlo también - No pude encontrar un idiota ni nada ... – Tim

+2

Esto es como la vieja broma. Paciente: duele cuando hago esto. Doctor: No hagas eso. En C++, no hagas una colección de "nada". –

Respuesta

11

C++ no tiene un objeto base del que hereden todos los objetos, a diferencia de Java. El enfoque habitual para lo que quiere hacer sería usar templates. Todos los contenedores en la biblioteca estándar de C++ usan este enfoque.

A diferencia de Java, C++ no se basa en el polimorfismo/herencia para implementar contenedores genéricos. En Java, todos los objetos heredan de Object, por lo que cualquier clase se puede insertar en un contenedor que toma un Object. Sin embargo, las plantillas de C++ son compilaciones de tiempo de compilación que instruyen al compilador a generar realmente una clase diferente para cada tipo que use. Así, por ejemplo, si tiene:

template <typename T> 
class MyContainer { ... }; 

A continuación, puede crear un MyContainer que toma std::string objetos, y otro que lleva MyContainer int s.

MyContainer<std::string> stringContainer; 
stringContainer.insert("Blah"); 

MyContainer<int> intContainer; 
intContainer.insert(3342); 
+0

¿Es seguro para mí hacer una analogía mental de las plantillas con los genéricos de Java? – Tim

+2

No realmente. Son bastante diferentes, incluso si la sintaxis es superficialmente la misma. –

+3

Para elaborar, los genéricos de Java son un mecanismo de tiempo de ejecución que se basa en la herencia y el polimorfismo. Las plantillas C++ son construcciones en tiempo de compilación que instruyen al compilador a generar código para cada tipo parametrizado. Ver mi respuesta editada para más información. –

3

Lo que está buscando son plantillas. Le permiten hacer clases y funciones que le permiten tomar cualquier tipo de datos.

1

Deberías poder lanzar un void* en un string* usando modelos de estilo C estándar. Recuerde que una referencia no se trata como un puntero cuando se usa, se trata como un objeto normal. Por lo tanto, si está pasando un valor por referencia a una función, igual debe redefinirlo para obtener su dirección.

Sin embargo, como han dicho otros, una mejor manera de hacer esto es con plantillas

+1

Los moldes estilo C deben desaconsejarse en C++. –

2

plantillas son la manera estática para hacer esto. Se comportan como los genéricos Java y C# pero son 100% estáticos (tiempo de compilación). Si necesita almacenar diferentes tipos de objetos en el mismo contenedor, use esto (otras respuestas describen esto muy bien).

Sin embargo, si necesita almacenar diferentes tipos de objetos en el mismo contenedor, puede hacerlo de forma dinámica, almacenando punteros en una clase base.Por supuesto, usted tiene que definir su propia jerarquía de objetos, ya que no hay tal clase de "objeto" en C++:

#include <list> 

class Animal { 
public: 
    virtual ~Animal() {} 
}; 

class Dog : public Animal { 
public: 
    virtual ~Dog() {} 
}; 

class Cat : public Animal { 
public: 
    virtual ~Cat() {} 
}; 

int main() { 
    std::list<Animal*> l; 
    l.push_back(new Dog); 
    l.push_back(new Cat); 

    for (std::list<Animal*>::iterator i = l.begin(); i!= l.end(); ++i) 
     delete *i; 

    l.clear(); 

    return 0; 
} 

Un puntero inteligente es más fácil de usar. Ejemplo con boost::smart_ptr:

std::list< boost::smart_ptr<Animal> > List; 
List.push_back(boost::smart_ptr<Animal>(new Dog)); 
List.push_back(boost::smart_ptr<Animal>(new Cat)); 
List.clear(); // automatically call delete on each stored pointer 
9

Usted puede echar un vistazo a cualquier clase de impulso ::. Es seguro, puede colocarlo en colecciones estándar y no necesita vincularlo con ninguna biblioteca, la clase se implementa en el archivo de encabezado.

Se le permite escribir código como este:

#include <list> 
#include <boost/any.hpp> 

typedef std::list<boost::any> collection_type; 

void foo() 
{ 
    collection_type coll; 
    coll.push_back(boost::any(10)); 
    coll.push_back(boost::any("test")); 
    coll.push_back(boost::any(1.1)); 
} 

documentación completa está aquí: http://www.boost.org/doc/libs/1_40_0/doc/html/any.html

1
static_cast<char*>(str.c_str()) 

parece extraño para mí. str.c_str() recupera la cadena tipo C, pero con el tipo const char *, y para convertir a char * normalmente usaría const_cast<char *>(str.c_str()). Excepto que eso no es bueno, ya que te estarías entrometiendo con las partes internas de un string. ¿Estás seguro de que no recibiste una advertencia sobre eso?

Debería poder usar static_cast<void *>(&str). El mensaje de error que recibiste me sugiere que tienes algo más equivocado, así que si pudieras publicar el código podríamos mirarlo. (El tipo de datos std::string& es una referencia a string, no un puntero a uno, por lo que el mensaje de error es correcto. Lo que no sé es cómo obtuvo una referencia en lugar de un puntero)

Y, sí , esto es detallado. Está destinado a ser. En general, se considera que el casting es un mal olor en un programa de C++, y Stroustrup quería que los moldes fueran fáciles de encontrar. Como se ha discutido en otras respuestas, la forma correcta de construir una estructura de datos de tipo base arbitrario es mediante el uso de plantillas, no moldes y punteros.

Cuestiones relacionadas