2008-10-03 3 views
6

Es sieht aus wie ich ein grundlegendes Missverständnis über C++ hatte: <C++ Alternativen * Zeiger auf void (dh keine Vorlagen)

Ich mag die polymorphe Containerlösung. Danke SO, für das Bringen zu dieser Aufmerksamkeit :)

So, wir müssen ein relativ generisches Behälterart-Objekt verursachen. Es kommt auch vor, einige Geschäftslogik einzukapseln. Wir müssen jedoch im Wesentlichen beliebige Daten in diesem Container speichern - von primitiven Datentypen bis hin zu komplexen Klassen.

So würde man sofort auf die Idee einer Template-Klasse springen und damit fertig sein. Ich habe jedoch festgestellt, C++ Polymorphismus und Vorlagen nicht gut zusammen spielen. Da es eine komplexe Logik gibt, die wir arbeiten müssen, bleibe ich lieber bei Vorlagen ODER Polymorphie und versuche nicht, C++ zu bekämpfen, indem ich beides mache.

Schließlich, da ich das eine oder andere machen möchte, würde ich Polymorphismus bevorzugen. Ich finde es viel einfacher, Einschränkungen wie "dieser Container enthält vergleichbare Typen" darzustellen - a la java.

Bring mich zum Thema der Frage: Am abstraktesten stelle ich mir vor, dass ich eine rein virtuelle "Container" -Schnittstelle haben könnte, die etwas wie "push (void * data) und pop (void * data)" hat (Für den Rekord versuche ich nicht wirklich, einen Stapel zu implementieren).

Allerdings mag ich nicht void * auf der obersten Ebene, nicht zu erwähnen, die Signatur wird jedes Mal ändern, wenn ich eine Einschränkung auf die Art von Daten hinzufügen möchten, mit denen ein konkreter Container arbeiten kann.

Zusammenfassung: Wir haben relativ komplexe Container, die verschiedene Möglichkeiten haben, Elemente abzurufen. Wir wollen in der Lage sein, die Beschränkungen für die Elemente zu variieren, die in die Container gelangen können. Elemente sollten mit mehreren Arten von Containern arbeiten (solange sie die Beschränkungen dieses bestimmten Containers erfüllen).

Edit: Ich sollte auch erwähnen, dass die Container selbst polymorph sein müssen. Das ist der Hauptgrund dafür, dass ich Templates nicht verwenden möchte.

Also - sollte ich meine Liebe für Java-Typ-Schnittstellen fallen lassen und mit Vorlagen gehen? Sollte ich void * verwenden und alles statisch werfen? Oder sollte ich mit einer leeren Klassendefinition "Element" gehen, die nichts deklariert und diese als meine Top-Level-Klasse in der "Element" -Hierarchie verwendet?

Einer der Gründe, warum ich Stapelüberlauf liebe, ist, dass viele der Antworten einige interessante Einblicke in andere Ansätze bieten, die ich nicht einmal in Betracht gezogen hätte. Vielen Dank im Voraus für Ihre Einsichten und Kommentare.

+0

was meinst du "Polymorphismus und Vorlagen spielen nicht gut zusammen"? –

+0

Speziell ein polymorpher Container - ich habe vergessen, diese Anforderung zu erwähnen. Soweit ich weiß, kann es nicht gemacht werden .. aber dann, was weiß ich. – Voltaire

+0

> Ich habe die Notwendigkeit, dass die Behälter selbst polymorph sind. Meine Antwort entsprechend aktualisiert. – Lev

Antwort

3

Können Sie keine Root-Container-Klasse haben, die Elemente enthält:

class Item 
{ 
public: 
    virtual void SomeMethod() = 0; 
}; 

class ConcreteItem 
{ 
public: 
    virtual void SomeMethod() 
    { 
     // Do something 
    } 
}; 

void AddItemToContainer(Container<Item> &container, Item *item) 
{ 
    container.push(item); 
} 

... 

List<Item> listInstance; 
AddItemToContainer(listInstance, new ConcreteItem()); 
listInstance.InvokeSomeMethodOnAllItems(); 

Dies gibt Ihnen die Container-Schnittstelle in ein typsicher:

template <typename T> 
class Container 
{ 
public: 

    // You'll likely want to use shared_ptr<T> instead. 
    virtual void push(T *element) = 0; 
    virtual T *pop() = 0; 
    virtual void InvokeSomeMethodOnAllItems() = 0; 
}; 

template <typename T> 
class List : public Container<T> 
{ 
    iterator begin(); 
    iterator end(); 
public: 
    virtual void push(T *element) {...} 
    virtual T* pop() { ... } 
    virtual void InvokeSomeMethodOnAllItems() 
    { 
     for(iterator currItem = begin(); currItem != end(); ++currItem) 
     { 
      T* item = *currItem; 
      item->SomeMethod(); 
     } 
    } 
}; 

Diese Container können dann um polymorph übergeben werden generische Art und Weise.

Wenn Sie Einschränkungen auf die Art der Elemente hinzufügen möchten, die enthalten sein können, können Sie etwas tun:

class Item 
{ 
public: 
    virtual void SomeMethod() = 0; 
    typedef int CanBeContainedInList; 
}; 

template <typename T> 
class List : public Container<T> 
{ 
    typedef typename T::CanBeContainedInList ListGuard; 
    // ... as before 
}; 
+0

Das habe ich mir gedacht. Das einzige Problem ist, dass Item im Wesentlichen eine leere Klasse ist. Soweit ich das beurteilen kann, kann ich keine Beschränkungen für das festlegen, was speziell im Container gespeichert ist, daher gibt es wirklich keine gemeinsame Schnittstelle, die ich extrahieren kann. Aber vielleicht ist das nicht so schlimm. – Voltaire

+0

Auch .. Ich glaube nicht, dass Sie virtuelle Funktionen in der Vorlage haben können. Ein weiteres großes Problem;) – Voltaire

+0

Sie können virtuelle Funktionen in einer Vorlage haben. – Lev

5

Polymorphismus und Vorlagen spielen sehr gut zusammen, wenn Sie sie richtig verwenden.

Wie auch immer, ich verstehe, dass Sie nur eine Art von Objekten in jeder Container-Instanz speichern möchten. Wenn ja, verwenden Sie Vorlagen. Dies verhindert, dass Sie versehentlich den falschen Objekttyp speichern.

Wie Container-Schnittstellen: Je nach Ihrem Design, können Sie vielleicht auch Vorlagen erstellen, und dann haben sie Methoden wie void push(T* new_element). Denken Sie darüber nach, was Sie über das Objekt wissen werden, wenn Sie es einem Container (eines unbekannten Typs) hinzufügen möchten. Woher kommt das Objekt überhaupt? Eine Funktion, die void* zurückgibt? Weißt du, dass es vergleichbar sein wird?Wenn mindestens alle Klassen gespeicherter Objekte in Ihrem Code definiert sind, können Sie sie alle von einem gemeinsamen Vorgänger erben lassen, z. B. Storable, und Storable* anstelle von void* verwenden.

Jetzt, wenn Sie sehen, dass Objekte immer zu einem Container durch eine Methode wie hinzugefügt werden, dann wirklich keinen Mehrwert, wenn der Container eine Vorlage wird. Aber dann weißt du, dass es Storables speichern sollte.

+0

Obwohl dies falsch sein könnte - ich denke, es ist sicher anzunehmen, dass ja, jeder Container ein Element eines Typs speichern wird. Ich habe jedoch die Notwendigkeit, dass die Behälter selbst polymorph sind. Das finde ich an Templates schwierig. – Voltaire

+0

"Wenn Sie sie richtig benutzen" = Untergang vieler angeblich guter Ideen. – DarenW

5

Die einfache Sache ist, eine abstrakte Basisklasse mit dem Namen Container zu definieren und sie für jede Art von Element, die Sie speichern möchten, zu untergliedern. Dann können Sie jede Standard-Sammlungsklasse (std::vector, std::list usw.) verwenden, um Zeiger auf Container zu speichern. Denken Sie daran, dass Sie, da Sie Zeiger speichern würden, mit ihrer Zuordnung/Freigabe umgehen müssten.

Die Tatsache, dass Sie jedoch eine einzelne Sammlung zum Speichern von Objekten so unterschiedlicher Art benötigen, deutet darauf hin, dass mit dem Design Ihrer Anwendung etwas nicht stimmt. Es ist möglicherweise besser, die Geschäftslogik erneut zu überprüfen, bevor Sie diesen übergenerischen Container implementieren.

13

Sie können einen Standardcontainer von boost::any verwenden, wenn Sie wirklich willkürliche Daten in den Container speichern.

Es klingt eher wie Sie eher so etwas wie ein boost::ptr_container haben würden, wo alles, was kann in dem Behälter von einem Basistyp abzuleiten hat gespeichert werden, und der Behälter selbst kann nur Sie in den Basistyp Referenz der geben.

1

Bei der Verwendung von Polymorphismus bleiben grundsätzlich eine Basisklasse für den Container und abgeleitete Klassen für die Datentypen übrig. Die Basisklasse/abgeleitete Klassen können in beiden Richtungen so viele virtuelle Funktionen haben, wie Sie benötigen.

Dies würde natürlich bedeuten, dass Sie die primitiven Datentypen auch in abgeleitete Klassen einschließen müssen. Wenn Sie die Verwendung von Vorlagen insgesamt überdenken würden, würde ich hier die Vorlagen verwenden. Erstellen Sie eine abgeleitete Klasse aus der Basis, die eine Vorlage ist, und verwenden Sie diese für die primitiven Datentypen (und andere, bei denen Sie keine weitere Funktionalität benötigen, als die Vorlage bereitstellt).

Vergiss nicht, dass du dein Leben durch typedefs für jeden der Vorlagen erleichtern kannst - besonders, wenn du später einen von ihnen in eine Klasse verwandeln musst.

3

Erstens, Templates und Polymorphie sind orthogonale Konzepte und sie spielen gut zusammen. Als nächstes, warum willst du eine spezifische Datenstruktur? Was ist mit den STL oder Boost-Datenstrukturen (speziell pointer containter) funktioniert nicht für Sie.

Angesichts Ihrer Frage klingt es so, als würden Sie die Erbschaft in Ihrer Situation missbrauchen. Es ist möglich, "constraints" zu erstellen, was in Ihren Containern enthalten ist, besonders wenn Sie Vorlagen verwenden. Diese Einschränkungen können über das hinausgehen, was Ihr Compiler und Linker Ihnen geben wird. Es ist in der Tat schwieriger mit Vererbung und Fehler sind eher für die Laufzeit übrig.

+1

Der Container selbst hat unabhängig von den gespeicherten Daten interessante Eigenschaften. Und es wäre schön, wenn es polymorph wäre. Es könnte eine Open-Source-Bibliothek geben, die nahe an dem ist, was wir brauchen, aber ich bezweifle, dass es genau unseren Anforderungen entspricht. – Voltaire

1

Sie können auch prüfen, The Boost Concept Check Library (BCCL) wollen die entworfen, um Einschränkungen für die zu schaffen, Template-Parameter von Template-Klassen, in diesem Fall Ihre Container.

Und um noch einmal zu wiederholen, was andere gesagt haben, hatte ich noch nie Probleme damit, Polymorphie und Templates zu mischen, und ich habe ziemlich komplizierte Sachen mit ihnen gemacht.

0

Sie müssen Java-ähnliche Schnittstellen nicht verwenden und auch Vorlagen verwenden. Josh's suggestion eines generischen Basis-Templates Container würde es Ihnen sicherlich erlauben, polymorph Container und ihre Kinder herumzugeben, aber zusätzlich könnten Sie sicher Schnittstellen als abstrakte Klassen implementieren, um die enthaltenen Elemente zu sein. Es gibt keinen Grund, warum Sie könnte eine abstrakte IComparable Klasse nicht erstellen, wie Sie vorgeschlagen, so dass Sie eine polymorphe Funktion haben könnte wie folgt:

class Whatever 
{ 
    void MyPolymorphicMethod(Container<IComparable*> &listOfComparables); 
} 

Diese Methode jetzt jedes Kind von Container nehmen, die jede Klasse enthält Umsetzung IComparable, also wäre es extrem flexibel.