Soll ich ganze Objekte oder Zeiger auf Objekte in Containern speichern?

162

Ein neues System von Grund auf neu entwerfen. Ich werde die STL verwenden, um Listen und Karten bestimmter langlebiger Objekte zu speichern.

Frage: Sollte ich sicherstellen, dass meine Objekte Kopierkonstruktoren haben und Kopien von Objekten in meinen STL-Containern speichern, oder ist es im Allgemeinen besser, die Lebensdauer und den Umfang selbst zu verwalten und nur die Zeiger auf diese Objekte in meinen STL-Containern zu speichern?

Mir ist klar, dass die Details etwas kurz sind, aber ich suche nach der "theoretischen" besseren Antwort, wenn sie existiert, da ich weiß, dass beide Lösungen möglich sind.

Zwei sehr offensichtliche Nachteile beim Spielen mit Zeigern: 1) Ich muss die Zuordnung / Freigabe dieser Objekte in einem Bereich außerhalb der STL selbst verwalten. 2) Ich kann kein temporäres Objekt auf dem Stapel erstellen und es meinen Containern hinzufügen.

Fehlt mir noch etwas?

Stéphane
quelle
36
Gott, ich liebe diese Seite, das ist die GENAUE Frage, an die ich heute gedacht habe ... danke, dass du sie mir gestellt hast :-)
eviljack
2
Eine andere interessante Sache ist, dass wir prüfen sollten, ob der Zeiger tatsächlich zur Sammlung hinzugefügt wurde, und wenn dies nicht der Fall ist, sollten wir wahrscheinlich delete aufrufen, um Speicherlecks zu vermeiden ... if ((set.insert (Zeiger)). second = false) {Zeiger löschen;}
javapowered

Antworten:

68

Da die Leute sich auf die Effizienz der Verwendung von Zeigern einlassen.

Wenn Sie die Verwendung eines std :: vector in Betracht ziehen und wenn nur wenige Aktualisierungen vorliegen und Sie häufig über Ihre Sammlung iterieren und es sich um einen nicht polymorphen Typ handelt, ist das Speichern von Objektkopien effizienter, da Sie eine bessere Referenzlokalität erhalten.

Otoh, wenn Aktualisierungen häufig sind, werden durch das Speichern von Zeigern die Kosten für das Kopieren / Verschieben gespart.

Torbjörn Gyllebring
quelle
7
In Bezug auf die Cache-Lokalität kann das Speichern von Zeigern in Vektoren effizient sein, wenn sie zusammen mit einem benutzerdefinierten Allokator für die Zeiger verwendet werden. Der benutzerdefinierte Allokator muss sich um die Cache-Lokalität kümmern, z. B. durch Platzierung neu (siehe en.wikipedia.org/wiki/Placement_syntax#Custom_allocators ).
Amit
47

Das hängt wirklich von Ihrer Situation ab.

Wenn Ihre Objekte klein sind und das Kopieren des Objekts leicht ist, ist das Speichern der Daten in einem STL-Container meiner Meinung nach unkompliziert und einfacher zu verwalten, da Sie sich nicht um die Verwaltung auf Lebenszeit kümmern müssen.

Wenn Ihre Objekte groß sind und ein Standardkonstruktor keinen Sinn ergibt oder Kopien von Objekten teuer sind, ist das Speichern mit Zeigern wahrscheinlich der richtige Weg.

Wenn Sie Zeiger auf Objekte verwenden möchten, sehen Sie sich die Boost Pointer Container Library an . Diese Boost-Bibliothek umschließt alle STL-Container zur Verwendung mit dynamisch zugewiesenen Objekten.

Jeder Zeigercontainer (z. B. ptr_vector) übernimmt das Eigentum an einem Objekt, wenn es dem Container hinzugefügt wird, und verwaltet die Lebensdauer dieser Objekte für Sie. Sie können auch auf alle Elemente in einem ptr_-Container als Referenz zugreifen. Auf diese Weise können Sie Dinge wie tun

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

Diese Klassen umschließen die STL-Container und arbeiten mit allen STL-Algorithmen, was sehr praktisch ist.

Es gibt auch Möglichkeiten, das Eigentum an einem Zeiger im Container auf den Aufrufer zu übertragen (über die Freigabefunktion in den meisten Containern).

Nick Haddad
quelle
38

Wenn Sie polymporhische Objekte speichern, müssen Sie immer eine Sammlung von Basisklassenzeigern verwenden.

Das heißt, wenn Sie vorhaben, verschiedene abgeleitete Typen in Ihrer Sammlung zu speichern, müssen Sie Zeiger speichern oder sich vom Slicing Deamon fressen lassen.

Torbjörn Gyllebring
quelle
1
Ich habe das Schneiden von Deamon geliebt!
Idichekop
22

Es tut uns leid, 3 Jahre nach dem Event zu springen, aber ein Warnhinweis hier ...

Bei meinem letzten großen Projekt bestand meine zentrale Datenstruktur aus einer Reihe ziemlich einfacher Objekte. Ungefähr ein Jahr nach Projektbeginn erkannte ich, dass das Objekt tatsächlich polymorph sein musste. Es dauerte einige Wochen schwieriger und unangenehmer Gehirnoperationen, um die Datenstruktur als Satz von Basisklassenzeigern zu fixieren und alle Kollateralschäden bei der Lagerung, dem Gießen usw. von Objekten zu behandeln. Ich habe ein paar Monate gebraucht, um mich davon zu überzeugen, dass der neue Code funktioniert. Dies hat mich übrigens dazu gebracht, mir Gedanken darüber zu machen, wie gut das Objektmodell von C ++ gestaltet ist.

Bei meinem aktuellen großen Projekt besteht meine zentrale Datenstruktur aus einer Reihe recht einfacher Objekte. Ungefähr ein Jahr nach Projektbeginn (was heute der Fall ist) wurde mir klar, dass das Objekt tatsächlich polymorph sein muss. Zurück zum Netz, habe diesen Thread gefunden und Nicks Link zur Boost-Zeiger-Container-Bibliothek gefunden. Genau das musste ich letztes Mal schreiben, um alles zu reparieren, also werde ich es dieses Mal versuchen.

Die Moral für mich jedenfalls: Wenn Ihre Spezifikation nicht zu 100% in Stein gemeißelt ist, suchen Sie nach Hinweisen, und Sie können sich möglicherweise später viel Arbeit sparen.

EML
quelle
Spezifikationen sind nie in Stein gemeißelt. Ich denke nicht, dass Sie ausschließlich Zeigercontainer verwenden sollten, obwohl Boost-Zeigercontainer diese Option viel attraktiver zu machen scheinen. Ich bin skeptisch, dass Sie Ihr gesamtes Programm auf einmal überarbeiten müssen, wenn Sie entscheiden, dass ein Objektcontainer in einen Zeigercontainer konvertiert werden soll. Dies kann bei einigen Designs der Fall sein. In diesem Fall handelt es sich um ein fragiles Design. In diesem Fall geben Sie Ihrem Problem nicht die "Schwäche" der Objektcontainer.
Allyourcode
Sie hätten das Element im Vektor mit Wertesemantik belassen und das polymorphe Verhalten darin ausführen können.
Billy ONeal
19

Warum nicht das Beste aus beiden Welten herausholen: Machen Sie einen Container mit intelligenten Zeigern (wie boost::shared_ptroder std::shared_ptr). Sie müssen den Speicher nicht verwalten und müssen sich nicht mit großen Kopiervorgängen befassen.

Branan
quelle
Inwiefern unterscheidet sich dieser Ansatz von dem, was Nick Haddad mithilfe der Boost Pointer Container Library vorgeschlagen hat?
Thorsten Schöning
10
@ ThorstenSchöning std :: shared_ptr fügt keine Abhängigkeit vom Boost hinzu.
James Johnston
Sie können keine gemeinsam genutzten Zeiger verwenden, um Ihren Polymorphismus zu behandeln, sodass Sie diese Funktion bei diesem Ansatz möglicherweise verpassen würden, es sei denn, Sie setzen explizit Zeiger um
auserdude
11

Im Allgemeinen ist das Speichern der Objekte direkt im STL-Container am besten, da es am einfachsten, effizientesten und am einfachsten für die Verwendung des Objekts ist.

Wenn Ihr Objekt selbst eine nicht kopierbare Syntax hat oder ein abstrakter Basistyp ist, müssen Sie Zeiger speichern (am einfachsten ist die Verwendung von shared_ptr).

Greg Rogers
quelle
4
Es ist nicht besonders effizient, wenn Ihre Objekte groß sind und Sie Elemente häufig verschieben.
Allyourcode
3

Sie scheinen den Unterschied gut zu verstehen. Wenn die Objekte klein und leicht zu kopieren sind, speichern Sie sie auf jeden Fall.

Wenn nicht, würde ich darüber nachdenken, intelligente Zeiger (nicht auto_ptr, ein intelligenter Zeiger mit Ref-Zählung) für diejenigen zu speichern, die Sie auf dem Heap zuweisen. Wenn Sie sich für intelligente Zeiger entscheiden, können Sie natürlich keine temporären Stapel zugewiesenen Objekte speichern (wie Sie gesagt haben).

@ Torbjörn macht einen guten Punkt über das Schneiden.

Lou Franco
quelle
1
Oh, und nie jemals jemals eine Sammlung von auto_ptr ist erstellen
Torbjörn Gyllebring
Richtig, auto_ptr ist kein intelligenter Zeiger - es zählt nicht.
Lou Franco
auto_ptr hat auch keine zerstörungsfreie Kopiersemantik. Durch die Zuweisung eines auto_ptr von onebis anotherwird die Referenz von freigegeben oneund geändert one.
Andy Finkenstadt
2

Wenn auf die Objekte an anderer Stelle im Code verwiesen werden soll, speichern Sie sie in einem Vektor von boost :: shared_ptr. Dadurch wird sichergestellt, dass Zeiger auf das Objekt gültig bleiben, wenn Sie die Größe des Vektors ändern.

Dh:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

Wenn sonst niemand Zeiger auf die Objekte speichert oder die Liste nicht wächst und schrumpft, speichern Sie sie einfach als einfache alte Objekte:

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

quelle
1

Diese Frage nervt mich schon eine Weile.

Ich möchte Zeiger speichern, habe aber einige zusätzliche Anforderungen (SWIG lua-Wrapper), die möglicherweise nicht für Sie gelten.

Der wichtigste Punkt in diesem Beitrag ist, ihn selbst mit Ihren Objekten zu testen

Ich habe dies heute getan, um die Geschwindigkeit des Aufrufs einer Mitgliedsfunktion für eine Sammlung von 10 Millionen Objekten 500 Mal zu testen.

Die Funktion aktualisiert x und y basierend auf xdir und ydir (alle Float-Member-Variablen).

Ich habe eine std :: list verwendet, um beide Objekttypen zu speichern, und festgestellt, dass das Speichern des Objekts in der Liste etwas schneller ist als die Verwendung eines Zeigers. Auf der anderen Seite war die Leistung sehr ähnlich, sodass es darauf ankommt, wie sie in Ihrer Anwendung verwendet werden.

Als Referenz haben die Zeiger mit -O3 auf meiner Hardware 41 Sekunden und die Rohobjekte 30 Sekunden gedauert.

Meleneth
quelle