Domanda

In Java, si può avere un Elenco di Oggetti.È possibile aggiungere oggetti di diversi tipi, quindi recuperare, controllare il loro tipo, e di eseguire l'azione appropriata per quel tipo.
Per esempio:(mi scuso se il codice non è esattamente corretto, sto andando a 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
}

Qual è il modo migliore per replicare questo comportamento in C++?

È stato utile?

Soluzione

Il tuo esempio con Spinta.Variante e un visitatore:

#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 buona di utilizzare questa tecnica è che se poi si aggiunge un altro tipo di variante e si dimentica di modificare un visitatore comprende questo tipo, non verrà compilato.Si sono a supporto di ogni possibile caso.Considerando che, se si utilizza un interruttore o a cascata, se le istruzioni, è facile dimenticare di effettuare il cambiamento, ovunque e introdurre un bug.

Altri suggerimenti

boost::variant è simile a dirkgently suggerimento di boost::any, ma supporta il modello di visitatore, il che significa che è più facile aggiungere un codice specifico in un secondo momento. Inoltre, alloca i valori nello stack anziché utilizzare l'allocazione dinamica, portando a un codice leggermente più efficiente.

MODIFICA: Come sottolineato da litb nei commenti, l'uso di variant anziché any significa che puoi contenere valori solo da uno di un elenco di tipi prespecificato. Questo è spesso un punto di forza, anche se potrebbe essere una debolezza nel caso del richiedente.

Ecco un esempio (non usando lo schema Visitatore però):

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

(E sì, dovresti tecnicamente usare vector<T>::size_type invece di int per il tipo di i e dovresti usare tecnicamente vector<T>::iterator invece, ma sto cercando di mantenerlo semplice.)

C ++ non supporta contenitori eterogenei.

Se non hai intenzione di usare boost l'hacking è creare una classe fittizia e avere tutte le diverse classi derivate da questa classe fittizia. Crea un contenitore a tua scelta per contenere oggetti classe fittizi e sei pronto per partire.

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
}

Se hai intenzione di utilizzare boost::any puoi provare boost::variant . Qui è un esempio dell'uso di <= >.

Puoi trovare questo eccellente articolo di due importanti esperti di C ++ di interesse.

Ora, <=> è un'altra cosa da tenere presente come j_random_hacker menzionato. Quindi, ecco un confronto per avere una buona idea di cosa usare.

Con un <=> il codice sopra sarebbe simile a questo:

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

Quanto spesso questo genere di cose è effettivamente utile? Ho programmato in C ++ per diversi anni, su diversi progetti, e in realtà non ho mai desiderato un contenitore eterogeneo. Potrebbe essere comune in Java per qualche motivo (ho molta meno esperienza Java), ma per qualsiasi uso dato in un progetto Java potrebbe esserci un modo per fare qualcosa di diverso che funzionerà meglio in C ++.

Il C ++ ha una maggiore enfasi sulla sicurezza dei tipi rispetto a Java, e questo è molto poco sicuro.

Detto questo, se gli oggetti non hanno nulla in comune, perché li conservi insieme?

Se hanno qualcosa in comune, puoi creare una classe da cui ereditare; in alternativa, usa boost :: any. Se ereditano, hanno funzioni virtuali da chiamare o usano dynamic_cast & Lt; & Gt; se proprio devi.

Vorrei solo sottolineare che l'uso del cast di tipi dinamici per ramificarsi in base al tipo spesso suggerisce dei difetti nell'architettura. La maggior parte delle volte puoi ottenere lo stesso effetto usando le funzioni virtuali:

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

Purtroppo non esiste un modo semplice per farlo in C ++. Devi creare tu stesso una classe base e derivare tutte le altre classi da questa classe. Crea un vettore di puntatori di classe base e quindi utilizza dynamic_cast (che viene fornito con il proprio overhead di runtime) per trovare il tipo effettivo.

Solo per completezza di questo argomento, voglio menzionare che puoi effettivamente farlo con C puro usando void * e poi lanciandolo in qualunque cosa debba essere (ok, il mio esempio non è C puro poiché usa vettori ma questo mi fa risparmiare un po 'di codice). Funzionerà se sai di che tipo sono i tuoi oggetti o se memorizzi un campo da qualche parte che lo ricorda. Sicuramente NON vuoi farlo, ma ecco un esempio per dimostrare che è possibile:

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

Mentre non è possibile memorizzare i tipi primitivi in contenitori, è possibile creare un tipo primitivo classi wrapper che sarà simile a Java autoboxed tipi primitivi (nel tuo esempio il primitivo digitato i valori letterali sono effettivamente autoboxed);le istanze di cui appaiono nel codice C++ (e può (quasi) essere utilizzati come variabili primitive/membri di dati.

Vedere Oggetto Wrapper per il Built-In Tipi da Algoritmi e Strutture di dati con Object-Oriented Design Pattern in C++.

Con l'oggetto avvolto è possibile utilizzare il c++ typeid() operatore per confrontare il tipo.Io sono abbastanza sicuro che il confronto seguente sistema:if (typeid(o) == typeid(Int)) [dove Int sarebbe avvolto classe per l'int di tipo primitivo, ecc...] (altrimenti è sufficiente aggiungere una funzione al primitivo wrapper che restituisce un typeid, e quindi:if (o.get_typeid() == typeid(Int)) ...

Detto questo, riguardo al tuo esempio, questo è il codice di odore per me.A meno che questo è l'unico posto dove si sta verificando il tipo di oggetto, Sarei propenso a utilizzare il polimorfismo (soprattutto se si hanno altri metodi/funzioni specifiche rispetto al tipo).In questo caso vorrei utilizzare il primitivo wrapper aggiunta di un interfacciato classe di dichiarare la differita metodo (si fa 'fare cose'), che verrà implementato da ogni avvolto classi primitive.Con questo si sarebbe in grado di utilizzare il contenitore iteratore di eliminare i tuoi se la dichiarazione (di nuovo, se è solo per questo un confronto di tipo di impostazione differite metodo di uso del polimorfismo proprio per questo sarebbe eccessivo).

Sono abbastanza inesperto, ma ecco cosa farei con-

  1. Crea una classe base per tutte le classi che devi manipolare.
  2. Scrivi classe contenitore / riutilizza classe contenitore. (Rivisto dopo aver visto altre risposte -Il mio punto precedente era troppo enigmatico.)
  3. Scrivi un codice simile.

Sono sicuro che sia possibile una soluzione molto migliore. Sono anche sicuro che sia possibile una spiegazione migliore. Ho imparato che ho delle cattive abitudini di programmazione in C ++, quindi ho provato a trasmettere la mia idea senza entrare nel codice.

Spero che questo aiuti.

A parte il fatto, come molti hanno sottolineato, non puoi farlo, o, cosa più importante, più che probabile, davvero non vuoi.

Respingiamo il tuo esempio e consideriamo qualcosa di più vicino a un esempio di vita reale. Nello specifico, un po 'di codice che ho visto in un vero progetto open source. Ha tentato di emulare una CPU in una matrice di caratteri. Quindi inserisce nell'array un byte & Quot; op code & Quot ;, seguito da 0, 1 o 2 byte che possono essere un carattere, un numero intero o un puntatore a una stringa, in base al codice operativo. Per gestirlo, ha comportato un sacco di problemi.

La mia soluzione semplice: 4 stack separati < > s: Uno per il " opcode " enum e uno ciascuno per caratteri, ints e stringa. Prendi il successivo dallo stack del codice operativo e ti porterebbe quale degli altri tre a ottenere l'operando.

Esistono ottime possibilità che il tuo problema reale possa essere gestito in modo simile.

Bene, potresti creare una classe base e quindi creare classi che ereditano da essa. Quindi, memorizzali in uno std :: vector.

La risposta breve è ... non puoi.

La lunga risposta è ... dovresti definire la tua nuova erirarchia di oggetti che ereditano tutti da un oggetto base. In Java tutti gli oggetti alla fine discendono da & Quot; Oggetto & Quot ;, che è ciò che ti permette di farlo.

RTTI (informazioni sul tipo di tempo di esecuzione) in C ++ è sempre stato difficile, in particolare il cross-compilatore.

L'opzione migliore è utilizzare STL e definire un'interfaccia per determinare il tipo di oggetto:

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top