2013-07-08 5 views
16

Betrachten wir die folgenden KlassenWas ist das bevorzugte C++ - Idiom, um eine Sammlung von polymorphen Objekten zu besitzen?

class Base { 
public: 
    virtual void do_stuff() = 0; 
}; 

class Derived : public Base { 
public 
    virtual void do_stuff() { std::cout << "I'm useful"; }; 
}; 

die wir nun sagen, dass ich eine andere Klasse verantwortlich für den Besitz von Objekten Base ‚s abgeleiteten Typen haben wollen und durchlaufen sie ihre do_stuff() Methode aufrufen. Es sieht so aus, aber ich weiß nicht, was T als

class Owner { 
public: 
    void do_all_stuff() { 
     //iterate through all items and call do_stuff() on them 
    } 

    void add_item(T item) { 
     items.push_back(item); 
    } 

    vector<T> items; 
} 

ich ein paar Möglichkeiten sehe erklärt werden soll:

T nicht Base sein, da ich nur Objekte hinzuzufügen wäre in der Lage von Betontyp Base, so das ist nicht in Frage.

T kann Base* oder Base&, aber jetzt muss ich den Anrufer von add_item() vertrauen mir einen Zeiger oder einen Verweis auf ein Objekt zu übergeben, die noch existieren wird, wenn ich es aus items abzurufen. Ich kann nicht delete die Elemente in Owner destructor, da ich nicht weiß, dass sie dynamisch zugewiesen wurden. Jedoch sollten sie delete sein, wenn sie es waren, was mich mit zweideutigem Besitz zurücklässt.

T kann Base* oder Base& und ich fügen Sie ein Base* create_item<DerivedT>() { return new DerivedT; } Methode Owner sein. Auf diese Weise weiß ich, dass der Zeiger gültig bleibt und ich ihn besitze, aber ich kann keinen Nichtstandardkonstruktor unter DerivedT aufrufen. Außerdem wird Owner für das Instanziieren von Objekten verantwortlich. Ich muss auch jedes Element in Owner Destruktor löschen, obwohl das kein großes Problem ist.

Grundsätzlich würde Ich mag Lage sein, etwas Ähnliches zu tun:

Owner owner; 

void add_one() { 
    Derived d; 

    owner.add_item(d); 
} 

void ready() { 
    owner.do_all_stuff(); 
} 

void main() { 
    for(int i = 0; i < 10; ++i) { 
     add_one(); 
    } 
    ready(); 
} 

Ich bin sicher, es ist etwas im Zusammenhang Semantik dort zu bewegen (ich die Objekte add_items() weitergegeben bewegen konnte, sie zu besitzen) aber ich kann immer noch nicht herausfinden, wie meine Sammlung erklärt werden würde.

Was ist das C++ - Idiom für diese Art von polymorphem Eigentum (insbesondere bei STL-Containern)?

+1

Wenn Sie Polymorphie wollen, werden Sie am Ende tun 'std :: container ', "mehrdeutig" oder nicht. Wenn Sie sich Sorgen um die Speicherverwaltung machen, können Sie auch einen 'Container >' erstellen. –

+8

Sie haben keine große Auswahl. Sie können eine Sammlung von (a) rohen Zeigern (b) intelligenten Zeigern haben. Das ist alles. Theoretisch kann (a) weiter unterteilt werden in (a.1) rohe Zeiger auf Objekte, die dem Container gehören, und (a.2) rohe Zeiger auf Objekte, die nicht dem Container gehören. Sie müssen nur eine Münze werfen und entscheiden. Wenn es auf beiden Seiten zur Ruhe kommt, benutze (b). Wenn es zur Ruhe kommt, auf der Kante stehend, benutze (a.1). Wenn es in der Luft schwebt, benutze (a.2). Einfach, nicht? –

+1

@ n.m. LOL ...... –

Antwort

18

Polymorphe Objekte müssen mit Zeiger oder Referenz behandelt werden. Da ihre Lebensdauer wahrscheinlich nicht an einen bestimmten Bereich gebunden ist, haben sie wahrscheinlich auch eine dynamische Speicherdauer, was bedeutet, dass Sie einen intelligenten Zeiger verwenden sollten.

Intelligente Zeiger wie std::shared_ptr und std::unique_ptr funktionieren problemlos in den Standard-Sammlungsarten.

std::vector<std::unique_ptr<Base>> 

diese Verwendung in Owner wie folgt aussieht:

class Owner { 
public: 
    void do_all_stuff() { 
     //iterate through all items and call do_stuff() on them 
    } 

    void add_item(std::unique_ptr<Base> item) { 
     items.push_back(std::move(item)); 
    } 

    vector<std::unique_ptr<Base>> items; 
} 

Das Argument Typ add_item die für das Hinzufügen eines Elements erforderlich Eigentumspolitik identifiziert, und fordert den Benutzer aus dem Weg zu gehen, um es zu vermasseln . Zum Beispiel können sie nicht zufällig einen rohen Zeiger mit einer impliziten, inkompatiblen Besitzersemantik übergeben, weil unique_ptr einen expliziten Konstruktor hat.

unique_ptr kümmert sich auch um das Löschen der Objekte im Besitz von Owner. Sie müssen zwar sicherstellen, dass Base über einen virtuellen Destruktor verfügt. Mit Ihrer aktuellen Definition erhalten Sie undefiniertes Verhalten. Polymorphe Objekte sollten praktisch immer einen virtuellen Destruktor haben.

+0

Vielen Dank für die tolle Antwort. Ich möchte nur darauf hinweisen, dass es zu einem späteren Zeitpunkt heißt: items.push_back (std :: move (item)); da ein unique_ptr nicht kopiert werden kann. –

+0

Außerdem sollte emplace_back meine Standardeinstellung sein.Aber es ist äquivalent wenn ich mich bewege, wenn ich mich richtig erinnere. – Klaim

+0

@Klaim Kein praktischer Unterschied zu "Pushback" in diesem Fall. –

1

Eigentlich können Sie keine Referenzen in AWL speichern, nur Zeiger oder echte Werte. Also T ist Base * Versuchen Sie andere Dinge, die Sie Ihren Compiler beschweren haben.

+0

Eigentlich möchtest du wahrscheinlich, dass dein Container 'shared_ptr ' oder 'unique_ptr ' enthält, im Gegensatz zu einem einfachen 'Base *'. –

+0

Es hängt von der Situation ab. Sie sind praktikable Optionen. –

+2

Eine andere Option ist reference_wrapper . – bstamour

2

aus Ihrem Kontext Unter der Annahme, dass Owner ist die alleinige Besitzer der Objekte enthalten sind, sollten T sein unique_ptr<Base> (wo unique_ptr von Schub oder std kommt je nach C++ 11 Verfügbarkeit). Dies erkennt ordnungsgemäß, dass es ausschließlich im Besitz des Containers ist, und zeigt zusätzlich die Semantik der Eigentumsübertragung in Ihrem add_item-Aufruf an.

+0

Eigentlich ist es in einem Container wahrscheinlich besser, 'std :: shared_ptr' anstatt 'unique_ptr' zu verwenden. –

+4

@James Kanze Können Sie erläutern, warum Sie in einem Einzeleigentümer-Container den zusätzlichen Overhead von 'shared_ptr' bevorzugen würden? –

+3

@JamesKanze Warum? 'unique_ptr' wäre auch meine erste Wahl. Zusätzlich zu besserer Leistung, weniger Speicherverbrauch und nicht erzwingender Synchronisation, spiegelt "unique_ptr" (vermutlich) die Besitz-Semantik genauer wider. – bames53

2

Andere Alternativen wert sind unter Berücksichtigung boost::ptr_container zu verwenden, oder noch besser, verwenden Sie eine Bibliothek wie adobe::poly oder boost::type_erasure für polymorphe Typen, wertbasierte Laufzeit-Polymorphismus-vermeidet auszubeuten die Notwendigkeit für Zeiger, Erbschaft usw.

Verwandte Themen