Pregunta

En Java, puede tener una Lista de objetos. Puede agregar objetos de varios tipos, luego recuperarlos, verificar su tipo y realizar la acción adecuada para ese tipo.
Por ejemplo: (disculpas si el código no es exactamente correcto, voy de memoria)

List<Object> list = new LinkedList<Object>();

list.add("Hello World!");
list.add(7);
list.add(true);

for (object o : list)
{
    if (o instanceof int)
        ; // Do stuff if it's an int
    else if (o instanceof String)
        ; // Do stuff if it's a string
    else if (o instanceof boolean)
        ; // Do stuff if it's a boolean
}

¿Cuál es la mejor manera de replicar este comportamiento en C ++?

¿Fue útil?

Solución

Su ejemplo usando Boost.Variant y un visitante:

#include <string>
#include <list>
#include <boost/variant.hpp>
#include <boost/foreach.hpp>

using namespace std;
using namespace boost;

typedef variant<string, int, bool> object;

struct vis : public static_visitor<>
{
    void operator() (string s) const { /* do string stuff */ }
    void operator() (int i) const { /* do int stuff */ }
    void operator() (bool b) const { /* do bool stuff */ }      
};

int main() 
{
    list<object> List;

    List.push_back("Hello World!");
    List.push_back(7);
    List.push_back(true);

    BOOST_FOREACH (object& o, List) {
        apply_visitor(vis(), o);
    }

    return 0;
}

Una cosa buena acerca del uso de esta técnica es que si más adelante agrega otro tipo a la variante y olvida modificar un visitante para incluir ese tipo, no se compilará. tiene para admitir todos los casos posibles. Mientras que, si usa un interruptor o declaraciones en cascada, es fácil olvidarse de hacer el cambio en todas partes e introducir un error.

Otros consejos

boost::variant es similar a dirkgently's sugerencia de boost::any, pero admite el patrón Visitante, lo que significa que es más fácil agregar código específico de tipo más adelante. Además, asigna valores en la pila en lugar de utilizar la asignación dinámica, lo que conduce a un código ligeramente más eficiente.

EDITAR: Como señala litb en los comentarios, usar variant en lugar de any significa que solo puede mantener valores de una de una lista de tipos previamente especificada. Esto a menudo es una fortaleza, aunque podría ser una debilidad en el caso del autor de la pregunta.

Aquí hay un ejemplo (sin embargo, sin usar el patrón Visitor):

#include <vector>
#include <string>
#include <boost/variant.hpp>

using namespace std;
using namespace boost;

...

vector<variant<int, string, bool> > v;

for (int i = 0; i < v.size(); ++i) {
    if (int* pi = get<int>(v[i])) {
        // Do stuff with *pi
    } else if (string* si = get<string>(v[i])) {
        // Do stuff with *si
    } else if (bool* bi = get<bool>(v[i])) {
        // Do stuff with *bi
    }
}

(Y sí, técnicamente debería usar vector<T>::size_type en lugar de int para el tipo de i, y técnicamente debería usar vector<T>::iterator en su lugar de todos modos, pero estoy tratando de mantenerlo simple).

C ++ no admite contenedores heterogéneos.

Si no va a utilizar boost, el truco es crear una clase ficticia y hacer que todas las clases diferentes se deriven de esta clase ficticia. Cree un contenedor de su elección para contener objetos de clase ficticios y estará listo para comenzar.

class Dummy {
   virtual void whoami() = 0;
};

class Lizard : public Dummy {
   virtual void whoami() { std::cout << "I'm a lizard!\n"; }
};


class Transporter : public Dummy {
   virtual void whoami() { std::cout << "I'm Jason Statham!\n"; }
};

int main() {
   std::list<Dummy*> hateList;
   hateList.insert(new Transporter());
   hateList.insert(new Lizard());

   std::for_each(hateList.begin(), hateList.end(), 
                 std::mem_fun(&Dummy::whoami));
   // yes, I'm leaking memory, but that's besides the point
}

Si va a utilizar boost::any puede intentar boost::variant . Aquí es un ejemplo del uso de <= >.

Puede encontrar este excelente artículo de dos destacados expertos en C ++ de interés.

Ahora, <=> es otra cosa a tener en cuenta como se menciona j_random_hacker . Entonces, aquí hay una comparación para tener una idea justa de qué usar.

Con un <=> el código anterior se vería así:

class Lizard {
   void whoami() { std::cout << "I'm a lizard!\n"; }
};

class Transporter {
   void whoami() { std::cout << "I'm Jason Statham!\n"; }
};

int main() {

   std::vector< boost::variant<Lizard, Transporter> > hateList;

   hateList.push_back(Lizard());
   hateList.push_back(Transporter());

   std::for_each(hateList.begin(), hateList.end(), std::mem_fun(&Dummy::whoami));
}

¿Con qué frecuencia es realmente útil este tipo de cosas? He estado programando en C ++ durante bastantes años, en diferentes proyectos, y nunca he querido un contenedor heterogéneo. Puede ser común en Java por alguna razón (tengo mucha menos experiencia en Java), pero para cualquier uso dado en un proyecto de Java puede haber una manera de hacer algo diferente que funcione mejor en C ++.

C ++ tiene un mayor énfasis en la seguridad de tipos que Java, y esto es muy inseguro.

Dicho esto, si los objetos no tienen nada en común, ¿por qué los almacenan juntos?

Si tienen cosas en común, puede hacer una clase para que hereden; alternativamente, use boost :: any. Si heredan, tienen funciones virtuales para llamar o usan dynamic_cast & Lt; & Gt; si realmente tienes que hacerlo.

Me gustaría señalar que el uso de la conversión dinámica de tipos para ramificarse según el tipo a menudo sugiere fallas en la arquitectura. La mayoría de las veces puede lograr el mismo efecto utilizando funciones virtuales:

class MyData
{
public:
  // base classes of polymorphic types should have a virtual destructor
  virtual ~MyData() {} 

  // hand off to protected implementation in derived classes
  void DoSomething() { this->OnDoSomething(); } 

protected:
  // abstract, force implementation in derived classes
  virtual void OnDoSomething() = 0;
};

class MyIntData : public MyData
{
protected:
  // do something to int data
  virtual void OnDoSomething() { ... } 
private:
  int data;
};

class MyComplexData : public MyData
{
protected:
  // do something to Complex data
  virtual void OnDoSomething() { ... }
private:
  Complex data;
};

void main()
{
  // alloc data objects
  MyData* myData[ 2 ] =
  {
    new MyIntData()
  , new MyComplexData()
  };

  // process data objects
  for ( int i = 0; i < 2; ++i ) // for each data object
  {
     myData[ i ]->DoSomething(); // no type cast needed
  }

  // delete data objects
  delete myData[0];
  delete myData[1];
};

Lamentablemente, no hay una manera fácil de hacer esto en C ++. Debe crear una clase base usted mismo y derivar todas las demás clases de esta clase. Cree un vector de punteros de clase base y luego use dynamic_cast (que viene con su propia sobrecarga de tiempo de ejecución) para encontrar el tipo real.

Solo para completar este tema, quiero mencionar que en realidad puedes hacer esto con C puro usando void * y luego convirtiéndolo en lo que sea necesario (ok, mi ejemplo no es C puro ya que usa vectores pero eso me ahorra algo de código). Esto funcionará si sabe de qué tipo son sus objetos, o si almacena un campo en algún lugar que lo recuerde. Seguramente NO DESEA hacer esto, pero aquí hay un ejemplo para demostrar que es posible:

#include <iostream>
#include <vector>

using namespace std;

int main() {

  int a = 4;
  string str = "hello";

  vector<void*> list;
  list.push_back( (void*) &a );
  list.push_back( (void*) &str );

  cout <<  * (int*) list[0] << "\t" << * (string*) list[1] << endl;

  return 0;
}

Si bien no puede almacenar tipos primitivos en contenedores, puede crear clases de contenedor de tipos primitivos que serán similares a los tipos primitivos autoboxed de Java (en su ejemplo, los literales tipados primitivos en realidad se están autoboxing); instancias de las cuales aparecen en el código C ++ (y pueden (casi) usarse) al igual que las variables primitivas / miembros de datos.

Consulte Contenedores de objetos para los tipos integrados de Estructuras de datos y algoritmos con patrones de diseño orientados a objetos en C ++ .

Con el objeto envuelto puede usar el operador c ++ typeid () para comparar el tipo. Estoy bastante seguro de que la siguiente comparación funcionará: if (typeid(o) == typeid(Int)) [donde Int sería la clase envuelta para el tipo primitivo int, etc. ...] (de lo contrario, simplemente agregue una función a sus envoltorios primitivos que devuelva un typeid y así: if (o.get_typeid() == typeid(Int)) ...

Dicho esto, con respecto a su ejemplo, esto tiene olor a código para mí. A menos que este sea el único lugar donde verifique el tipo de objeto, Me inclinaría a usar el polimorfismo (especialmente si tiene otros métodos / funciones específicos con respecto al tipo). En este caso, usaría los envoltorios primitivos agregando una clase interconectada que declara el método diferido (para hacer 'hacer cosas') que implementaría cada una de sus clases primitivas envueltas. Con esto, podría usar su iterador de contenedor y eliminar su declaración if (nuevamente, si solo tiene esta comparación de tipo, configurar el método diferido usando polimorfismo solo para esto sería exagerado).

Soy bastante inexperto, pero esto es con lo que iría-

  1. Cree una clase base para todas las clases que necesita manipular.
  2. Escribir clase contenedor / reutilizar clase contenedor. (Revisado después de ver otras respuestas: mi punto anterior era demasiado críptico).
  3. Escribir código similar.

Estoy seguro de que es posible una solución mucho mejor. También estoy seguro de que es posible una mejor explicación. Aprendí que tengo algunos malos hábitos de programación en C ++, así que intenté transmitir mi idea sin entrar en el código.

Espero que esto ayude.

Además del hecho, como la mayoría ha señalado, no puedes hacer eso, o lo que es más importante, lo más probable es que realmente no quieras hacerlo.

Descartemos su ejemplo y consideremos algo más cercano a un ejemplo de la vida real. Específicamente, un código que vi en un proyecto real de código abierto. Intentó emular una CPU en una matriz de caracteres. Por lo tanto, pondría en la matriz un byte & Quot; código de operación & Quot ;, seguido de 0, 1 o 2 bytes que podrían ser un carácter, un entero o un puntero a una cadena, en función de código de operación Para manejar eso, involucraba un montón de retoques.

Mi solución simple: 4 pilas separadas < > s: Una para " opcode " enumeración y uno para caracteres, entos y cuerdas. Tome el siguiente de la pila de código de operación, y le tomaría cuál de los otros tres para obtener el operando.

Existe una muy buena posibilidad de que su problema real pueda manejarse de manera similar.

Bueno, podría crear una clase base y luego crear clases que hereden de ella. Luego, guárdelos en un std :: vector.

La respuesta corta es ... no puedes.

La respuesta larga es ... tendrías que definir tu propia nueva jerarquía de objetos que todos heredan de un objeto base. En Java, todos los objetos finalmente descienden de & Quot; Object & Quot ;, que es lo que le permite hacer esto.

RTTI (información de tipo de tiempo de ejecución) en C ++ siempre ha sido difícil, especialmente el compilador cruzado.

Su mejor opción es usar STL y definir una interfaz para determinar el tipo de objeto:

public class IThing
{
   virtual bool isA(const char* typeName);
}

void myFunc()
{
   std::vector<IThing> things;

   // ...

   things.add(new FrogThing());
   things.add(new LizardThing());

   // ...

   for (int i = 0; i < things.length(); i++)
   {
       IThing* pThing = things[i];

       if (pThing->isA("lizard"))
       {
         // do this
       }
       // etc
   }
}

Mike

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top