Frage

In Java können Sie eine Liste von Objekten haben. Sie können Objekte von mehreren Typen hinzufügen, dann abrufen sie, überprüfen ihre Art, und führen Sie die entsprechende Aktion für diesen Typ.
Zum Beispiel: (Entschuldigungen, wenn der Code nicht ganz richtig ist, ich bin aus dem Gedächtnis gehen)

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
}

Was ist der beste Weg, um dieses Verhalten in C ++ zu replizieren?

War es hilfreich?

Lösung

Ihr Beispiel mit Boost.Variant und einem Besucher:

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

Eine gute Sache über diese Technik ist, dass, wenn später, Sie eine andere Art der Variante hinzufügen, und Sie vergessen, einen Besucher zu modifizieren, dass die Art aufzunehmen, wird es nicht kompilieren. Sie wurde jeden denkbaren Fall zu unterstützen. Während, wenn Sie einen Schalter oder Kaskadierung if-Anweisungen verwenden, ist es leicht zu vergessen, die Änderung überall zu machen und einen Fehler einzuführen.

Andere Tipps

boost::variant ist ähnlich Vorschlag des dirkgently von boost::any, sondern unterstützt das Besuchermuster, das heißt, es ist einfacher, später typspezifischen Code hinzufügen. Auch sie weist Werte auf dem Stapel anstatt dynamische Zuordnung, was zu etwas effizientem Code.

EDIT: Wie litb in den Kommentaren weist darauf hin, mit variant statt any bedeutet, dass Sie nur Werte aus einem aus einer vorgegebenen Liste von Typen halten können. Dies ist oft eine Kraft, obwohl es sich um eine Schwäche in der Fragesteller Fall sein könnte.

Hier ist ein Beispiel (nicht mit dem Besuchermuster, obwohl):

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

(Und ja, Sie technisch vector<T>::size_type statt int für i des Typs verwendet werden soll, und Sie sollten technisch vector<T>::iterator verwenden, anstatt wie auch immer, aber ich versuche es einfach zu halten.)

C ++ nicht unterstützt heterogene Container.

Wenn Sie nicht gehen, um den Hack zu verwenden boost ist eine Dummy-Klasse zu erstellen und haben alle die verschiedenen Klassen von dieser Dummy-Klasse abgeleitet werden. Erstellen Sie einen Container Ihrer Wahl Dummy-Klasse Objekte zu halten, und Sie sind bereit zu gehen.

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
}

Wenn Sie vorhaben, boost zu verwenden, können Sie versuchen, boost::any . Hier ist ein Beispiel für boost::any verwenden.

Sie können diese ausgezeichneten Artikel von zwei führenden C ++ Experten von Interesse rel="nofollow.

Nun ist boost::variant eine andere Sache zu suchen, wie j_random_hacker erwähnt. So, hier ist ein Vergleich rel="nofollow eine Vorstellung davon zu bekommen, was zu verwenden.

Mit einem boost::variant dem obigen Code würde wie folgt aussehen:

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

Wie oft ist diese Art der Sache wirklich nützlich? Ich war in C ++ Programmierung für ganz wenige Jahre an verschiedenen Projekten und habe nie wollte eigentlich einen heterogenen Behälter. Es kann in Java aus irgendeinem Grunde (ich habe viel weniger Java Erfahrung), aber für einen bestimmten Gebrauch davon in einem Java-Projekt könnte es sein, ein Weg, um etwas anderes zu tun, das wird besser funktionieren in C ++.

gemeinsam sein

C ++ hat eine stärkere Betonung auf die Typsicherheit als Java, und das ist sehr typ unsicher.

Das heißt, wenn die Objekte nichts gemein haben, warum Sie sie speichern zusammen?

Wenn sie Dinge tun haben gemeinsam, können Sie eine Klasse für sie, von erben; alternativ verwendet boost :: any. Wenn sie erben, haben virtuelle Funktionen aufzurufen, oder verwenden Sie dynamic_cast <>, wenn Sie wirklich müssen.

Ich möchte nur darauf hinweisen, dass die Verwendung dynamischer Typ Gießen, um basierend auf einem Ast auf Art oft auf Mängel in der Architektur hindeutet. Die meiste Zeit können Sie den gleichen Effekt mit virtuellen Funktionen erreichen:

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

Leider gibt es keine einfache Möglichkeit, dies zu tun, in C ++. Sie haben eine Basisklasse selbst erstellen und alle anderen Klassen von dieser Klasse ableiten. Erstellen Sie einen Vektor von Basisklassenzeigern und verwenden Sie dann dynamic_cast (die mit einem eigenen Laufzeit-Overhead kommt) den tatsächlichen Typ zu finden.

Nur der Vollständigkeit halber dieses Thema möchte ich erwähnen, dass Sie dies mit reinem C tatsächlich tun kann durch Leere mit * und dann in Gießen, was auch immer es sein muss (ok, ist mein Beispiel nicht rein C, da es Vektoren verwendet aber das spart mir einige Code). Das funktioniert, wenn Sie wissen, was geben Sie Ihre Objekte sind, oder wenn Sie ein Feld irgendwo speichern, die das erinnert. Sie sicherlich nicht wollen, dies zu tun, aber hier ist ein Beispiel zu zeigen, dass es möglich ist:

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

Während Sie nicht primitive Typen in Containern gespeichert werden können, können Sie primitive Art Wrapper-Klassen erstellen, die auf Java autoboxed primitiven Typen ähnlich sein werden (in Ihrem Beispiel die primitive typisiert Literale wird tatsächlich autoboxed); Instanzen von denen erscheinen in C ++ Code (und kann (fast) verwendet werden) genauso wie primitive Variablen / Datenelemente.

Siehe Objekt-Wrapper für die eingebauten Typen von Datenstrukturen und Algorithmen mit objektorientierten Design Patterns in C ++ .

Mit dem umwickelten Objekt können Sie den c ++ typeid Operator () verwenden, um die Art zu vergleichen. Ich bin ziemlich sicher, dass der folgende Vergleich funktioniert: if (typeid(o) == typeid(Int)) [wo Int würden die verpackte Klasse für den int primitiven Typen, etc ...] (Sonst einfach eine Funktion, um Ihre primitive Wrapper hinzufügen, die eine typeid zurückgibt und damit: if (o.get_typeid() == typeid(Int)) ...

aber sagen, dass in Bezug auf Ihr Beispiel hat dieser Code Geruch zu mir. Es sei denn, dies ist der einzige Ort, wo Sie die Art des Objekts überprüft, Ich würde geneigt sein, Polymorphismus zu verwenden (vor allem, wenn Sie andere Methoden / spezifische Funktionen in Bezug auf den Typ haben). In diesem Fall würde ich die primitiven Wrapper verwenden, um eine Schnittstelle Klasse erklärt die verzögerte Verfahren des Hinzufügen (zu tun ‚zu tun Sachen‘), die von jedem Ihrer gewickelten primitiven Klassen implementiert werden würde. Damit würden Sie in der Lage sein, Ihre Behälter Iterator verwenden und beseitigen Ihre if-Anweisung (wieder, wenn Sie nur diesen einen Vergleich des Typs haben, die Einrichtung die latente Methode Polymorphismus nur für diese Overkill wäre).

Ich bin ein ziemlich unerfahren, aber hier ist, was ich gehen würde mit -

  1. Erstellen Sie eine Basisklasse für alle Klassen müssen Sie manipulieren.
  2. Write Container-Klasse / Wiederverwendung Containerklasse. (Überarbeitet, nachdem er andere Antworten -My vorheriger Punkt zu kryptisch war.)
  3. Schreiben Sie einen ähnlichen Code.

Ich bin sicher, dass eine viel bessere Lösung möglich ist. Ich bin auch sicher, dass eine bessere Erklärung möglich ist. Ich habe gelernt, dass ich ein paar schlechte C ++ Programmierung Gewohnheiten haben, also habe ich versucht, meine Idee zu vermitteln, ohne in den Code zu bekommen.

Ich hoffe, das hilft.

Neben der Tatsache, da die meisten darauf hingewiesen haben, können Sie das nicht tun, oder was noch wichtiger ist, mehr als wahrscheinlich, die Sie wirklich wollen nicht.

Lassen Sie uns Ihr Beispiel entlassen, und etwas näher an einem realen Beispiel. Insbesondere sah einige Code, den ich in einem echten Open-Source-Projekt. Es versucht, eine CPU in einem Zeichen-Array zu emulieren. Daher wäre es das Array einen ein Byte „OP-Code“ put in, gefolgt von 0, 1 oder 2 Bytes, die ein Zeichen, eine ganze Zahl sein könnten, oder ein Zeiger auf eine Zeichenkette, basierend auf dem op-Code. Zu handhaben, dass es eine Menge von Bit-Fiedeln beteiligt.

Meine einfache Lösung: 4 separate Stapel <> s: Eine für die "opcode" Enum und jeweils eine für Zeichen, Ints und String. Nehmen Sie die nächste aus dem Opcode-Stack, und das würden Sie nehmen, welche die drei andere die Operanden zu erhalten.

Es gibt eine sehr gute Chance, Ihr eigentliches Problem kann in ähnlicher Weise behandelt werden.

Nun, man könnte eine Basisklasse erstellen und dann Klassen erstellen, die von ihm erben. Dann speichern Sie sie in einem std :: vector.

Die kurze Antwort ist ... Sie können nicht.

Die lange Antwort ist ... würden Sie Ihre eigene neue Hierarchie von Objekten definieren müssen, die alle von einem Basisobjekt erben. In Java alle Objekte absteigen schließlich von „Objekt“, das ist das, was Sie dies tun können.

RTTI (Run Time Type Information) in C ++ ist immer hart, vor allem Cross-Compiler.

Sie sind die beste Wahl ist STL zu verwenden und eine Schnittstelle, um den Objekttyp definieren, um zu bestimmen:

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top