Question

En Java, vous pouvez avoir une liste d'objets. Vous pouvez ajouter des objets de plusieurs types, puis les récupérer, vérifier leur type et effectuer l'action appropriée pour ce type.
Par exemple: (excuses si le code n’est pas tout à fait correct, je passe de mémoire)

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
}

Quel est le meilleur moyen de répliquer ce comportement en C ++?

Était-ce utile?

La solution

Votre exemple utilisant Boost.Variant et un visiteur:

#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;
}

L’utilisation de cette technique présente un avantage: si vous ajoutez ultérieurement un autre type à la variante et si vous oubliez de modifier un visiteur pour inclure ce type, il ne sera pas compilé. Vous devez prendre en charge tous les cas possibles. Tandis que si vous utilisez un commutateur ou des instructions if en cascade, il est facile d’oublier de faire le changement partout et d’introduire un bogue.

Autres conseils

boost::variant est similaire à dirkgently suggestion de boost::any, mais prend en charge le modèle de visiteur, ce qui signifie qu'il est plus facile d'ajouter du code spécifique au type ultérieurement. De plus, il alloue des valeurs sur la pile plutôt que d'utiliser l'allocation dynamique, ce qui conduit à un code légèrement plus efficace.

MODIFIER: Comme le souligne litb dans les commentaires, utiliser variant au lieu de any signifie que vous ne pouvez conserver les valeurs que de l'un des types prédéfinis. C’est souvent une force, bien que cela puisse constituer une faiblesse dans le cas du demandeur.

Voici un exemple (n'utilisant pas le modèle de visiteur):

#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
    }
}

(Et oui, vous devriez techniquement utiliser vector<T>::size_type au lieu de int pour le type de i, et vous devriez utiliser techniquement vector<T>::iterator à la place, mais j'essaie de garder les choses simples.)

C ++ ne prend pas en charge les conteneurs hétérogènes.

Si vous n'allez pas utiliser boost, le hack consiste à créer une classe fictive et à faire en sorte que toutes les différentes classes dérivent de cette classe fictive. Créez un conteneur de votre choix pour contenir des objets de classe factices et vous êtes prêt à partir.

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 vous envisagez d'utiliser boost::any, vous pouvez essayer boost::variant . Ici est un exemple d'utilisation <= >.

Vous trouverez peut-être cet excellent article par deux grands spécialistes de C ++ intéressants.

Maintenant, <=> est une autre chose à surveiller, comme indiqué dans j_random_hacker . Voici donc un comparaison pour avoir une idée juste de ce qu'il faut utiliser.

Avec un <=>, le code ci-dessus ressemblerait à ceci:

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));
}

À quelle fréquence ce genre de chose est-il réellement utile? Je programme en C ++ depuis plusieurs années, sur différents projets, et je n'ai jamais vraiment voulu de conteneur hétérogène. Cela peut être courant en Java pour une raison quelconque (j’ai beaucoup moins d’expérience Java), mais pour toute utilisation donnée dans un projet Java, il peut exister un moyen de faire quelque chose de différent qui fonctionnera mieux en C ++.

C ++ insiste davantage sur la sécurité des types que Java, ce qui est très dangereux pour les types.

Cela dit, si les objets n'ont rien en commun, pourquoi les stockez-vous ensemble?

S'ils ont des choses en commun, vous pouvez créer une classe dont ils hériteront; alternativement, utilisez boost :: any. S'ils héritent, ont des fonctions virtuelles à appeler ou utilisent dynamic_cast & Lt; & Gt; si vous devez vraiment.

Je voudrais juste souligner que l'utilisation du transtypage de type dynamique afin de créer une branche basée sur le type suggère souvent des failles dans l'architecture. La plupart du temps, vous pouvez obtenir le même effet en utilisant des fonctions virtuelles:

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];
};

Malheureusement, il n’ya pas de moyen facile de faire cela en C ++. Vous devez créer vous-même une classe de base et dériver toutes les autres classes de cette classe. Créez un vecteur de pointeurs de classe de base, puis utilisez dynamic_cast (qui vient avec sa propre surcharge d’exécution) pour trouver le type actuel.

Juste pour que ce sujet soit complet, je tiens à mentionner que vous pouvez réellement le faire avec du C pur en utilisant void *, puis en le transformant comme il se doit (ok, mon exemple n’est pas du C pur, car il utilise des vecteurs mais cela me sauve du code). Cela fonctionnera si vous connaissez le type de vos objets ou si vous stockez un champ quelque part qui s'en souvient. Vous ne voulez certainement pas faire cela, mais voici un exemple pour montrer que c'est possible:

#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;
}

Bien que vous ne puissiez pas stocker de types primitifs dans des conteneurs, vous pouvez créer des classes wrapper de type primitif qui seront similaires aux types primitifs à ciblage automatique de Java (dans votre exemple, les littéraux de type primitif sont en fait à classement automatique); dont des instances apparaissent dans le code C ++ (et peuvent (presque) être utilisées) exactement comme des variables primitives / membres de données.

Voir Les wrappers d'objet pour les types intégrés sur Structures de données et algorithmes avec modèles de conception orientés objet en C ++ .

Avec l'objet encapsulé, vous pouvez utiliser l'opérateur c ++ typeid () pour comparer le type. Je suis à peu près sûr que la comparaison suivante fonctionnera: if (typeid(o) == typeid(Int)) [où Int serait la classe encapsulée pour le type primitive int, etc ...] (sinon, ajoutez simplement une fonction à vos wrappers primitifs qui retourne un typeid et donc: if (o.get_typeid() == typeid(Int)) ...

Cela étant dit, en ce qui concerne votre exemple, cela a une odeur de code pour moi. À moins que ce ne soit le seul endroit où vous vérifiez le type de l'objet, Je serais enclin à utiliser le polymorphisme (surtout si vous avez d'autres méthodes / fonctions spécifiques en ce qui concerne le type). Dans ce cas, j'utiliserais les wrappers primitifs en ajoutant une classe interfacée déclarant la méthode différée (pour faire des "tâches") qui serait implémentée par chacune de vos classes primitives enveloppées. Avec cela, vous pourrez utiliser votre itérateur de conteneur et éliminer votre instruction if (si vous ne disposez que de cette comparaison de type, configurer la méthode différée en utilisant un polymorphisme juste pour cela serait excessif).

Je suis un peu inexpérimenté, mais voici ce que j'irais avec -

  1. Créez une classe de base pour toutes les classes que vous devez manipuler.
  2. Écrire une classe de conteneur / réutiliser une classe de conteneur. (Révisé après avoir vu d'autres réponses - Mon point précédent était trop cryptique.)
  3. Écrivez un code similaire.

Je suis sûr qu'une solution bien meilleure est possible. Je suis également certain qu'une meilleure explication est possible. J'ai appris que j'avais de mauvaises habitudes de programmation C ++ et j'ai donc essayé de transmettre mon idée sans entrer dans le code.

J'espère que cela aide.

En plus du fait, comme la plupart l’ont souligné, vous ne pouvez pas le faire, ou plus important encore, plus que probablement, vous ne voulez vraiment pas.

Laissons de côté votre exemple et considérons quelque chose de plus proche d’un exemple réel. Plus précisément, certains codes que j'ai vus dans un vrai projet open-source. Il a tenté d'émuler un cpu dans un tableau de caractères. Par conséquent, il mettrait dans le tableau un octet & "; Code d'opération &"; Suivi de 0, 1 ou 2 octets pouvant être un caractère, un entier ou un pointeur sur une chaîne, code d'opération. Pour gérer cela, il a fallu beaucoup de bricolage.

Ma solution simple: 4 piles séparées < > s: une pour l’opcode & "; &"; enum et un chacun pour chars, ints et string. Retirez le prochain élément de la pile de codes d'opération et vous obtiendrez lequel des trois autres pour obtenir l'opérande.

Il est fort probable que votre problème actuel puisse être traité de la même manière.

Eh bien, vous pouvez créer une classe de base, puis créer des classes qui en héritent. Ensuite, stockez-les dans un std :: vector.

La réponse courte est ... vous ne pouvez pas.

La réponse longue est ... vous devez définir votre propre nouvelle hiérarchie d'objets hérités d'un objet de base. En Java, tous les objets descendent finalement de & "Objet &"; Ce qui vous permet de le faire.

RTTI (informations de type à l'exécution) en C ++ a toujours été difficile, en particulier pour les compilateurs croisés.

Votre meilleure option est d'utiliser STL et de définir une interface afin de déterminer le type d'objet:

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top