Ich verwende einen Vektor von Zeigern auf Objekte. Diese Objekte werden von einer Basisklasse abgeleitet und dynamisch zugeordnet und gespeichert.
Zum Beispiel habe ich so etwas wie:
vector<Enemy*> Enemies;
und ich werde von der Enemy-Klasse ableiten und dann dynamisch Speicher für die abgeleitete Klasse zuweisen, wie folgt:
enemies.push_back(new Monster());
Was muss ich beachten, um Speicherverluste und andere Probleme zu vermeiden?
Antworten:
std::vector
verwaltet den Speicher wie immer für Sie, aber dieser Speicher besteht aus Zeigern, nicht aus Objekten.Dies bedeutet, dass Ihre Klassen im Speicher verloren gehen, sobald Ihr Vektor den Gültigkeitsbereich verlässt. Zum Beispiel:
#include <vector> struct base { virtual ~base() {} }; struct derived : base {}; typedef std::vector<base*> container; void foo() { container c; for (unsigned i = 0; i < 100; ++i) c.push_back(new derived()); } // leaks here! frees the pointers, doesn't delete them (nor should it) int main() { foo(); }
Sie müssen lediglich sicherstellen, dass Sie alle Objekte löschen, bevor der Vektor den Gültigkeitsbereich verlässt:
#include <algorithm> #include <vector> struct base { virtual ~base() {} }; struct derived : base {}; typedef std::vector<base*> container; template <typename T> void delete_pointed_to(T* const ptr) { delete ptr; } void foo() { container c; for (unsigned i = 0; i < 100; ++i) c.push_back(new derived()); // free memory std::for_each(c.begin(), c.end(), delete_pointed_to<base>); } int main() { foo(); }
Dies ist jedoch schwierig aufrechtzuerhalten, da wir uns daran erinnern müssen, eine Aktion auszuführen. Noch wichtiger ist, wenn eine Ausnahme zwischen der Zuweisung von Elementen und der Freigabeschleife auftreten würde, würde die Freigabeschleife niemals ausgeführt und Sie stecken trotzdem mit dem Speicherverlust fest! Dies wird als Ausnahmesicherheit bezeichnet und ist ein kritischer Grund, warum die Freigabe automatisch erfolgen muss.
Besser wäre es, wenn sich die Zeiger selbst löschen würden. Diese werden als intelligente Zeiger bezeichnet, und die Standardbibliothek bietet
std::unique_ptr
undstd::shared_ptr
.std::unique_ptr
stellt einen eindeutigen Zeiger (nicht freigegeben, Einzelbesitzer) auf eine Ressource dar. Dies sollte Ihr standardmäßiger intelligenter Zeiger sein und die vollständige Verwendung aller Rohzeiger vollständig ersetzen.auto myresource = /*std::*/make_unique<derived>(); // won't leak, frees itself
std::make_unique
fehlt im C ++ 11-Standard durch Versehen, aber Sie können selbst einen erstellen. Gehen Sie folgendermaßen vor, um ein direkt zu erstellenunique_ptr
(nicht empfohlen,make_unique
wenn Sie können):std::unique_ptr<derived> myresource(new derived());
Eindeutige Zeiger haben nur eine Bewegungssemantik. Sie können nicht kopiert werden:
auto x = myresource; // error, cannot copy auto y = std::move(myresource); // okay, now myresource is empty
Und das ist alles, was wir brauchen, um es in einem Container zu verwenden:
#include <memory> #include <vector> struct base { virtual ~base() {} }; struct derived : base {}; typedef std::vector<std::unique_ptr<base>> container; void foo() { container c; for (unsigned i = 0; i < 100; ++i) c.push_back(make_unique<derived>()); } // all automatically freed here int main() { foo(); }
shared_ptr
hat eine Referenzzählkopiesemantik; Es ermöglicht mehreren Eigentümern, das Objekt gemeinsam zu nutzen. Es verfolgt, wie vieleshared_ptr
s für ein Objekt existieren, und wenn das letzte nicht mehr existiert (diese Anzahl geht auf Null), gibt es den Zeiger frei. Durch das Kopieren wird lediglich die Referenzanzahl erhöht (und durch das Verschieben wird das Eigentum zu geringeren, fast kostenlosen Kosten übertragen). Sie erstellen sie mitstd::make_shared
(oder direkt wie oben gezeigt, aber dashared_ptr
interne Zuweisungen vorgenommen werden müssen, ist die Verwendung im Allgemeinen effizienter und technisch ausnahmsicherermake_shared
).#include <memory> #include <vector> struct base { virtual ~base() {} }; struct derived : base {}; typedef std::vector<std::shared_ptr<base>> container; void foo() { container c; for (unsigned i = 0; i < 100; ++i) c.push_back(std::make_shared<derived>()); } // all automatically freed here int main() { foo(); }
Denken Sie daran, dass Sie im Allgemeinen
std::unique_ptr
standardmäßig verwenden möchten, da es leichter ist. Zusätzlichstd::shared_ptr
kann konstruiert aus einem werdenstd::unique_ptr
(aber nicht umgekehrt), so dass es in Ordnung ist , klein zu beginnen.Alternativ können Sie einen Container verwenden, der zum Speichern von Zeigern auf Objekte erstellt wurde, z
boost::ptr_container
:#include <boost/ptr_container/ptr_vector.hpp> struct base { virtual ~base() {} }; struct derived : base {}; // hold pointers, specially typedef boost::ptr_vector<base> container; void foo() { container c; for (int i = 0; i < 100; ++i) c.push_back(new Derived()); } // all automatically freed here int main() { foo(); }
Während
boost::ptr_vector<T>
dies in C ++ 03 offensichtlich verwendet wurde, kann ich jetzt nicht über die Relevanz sprechen, da wir esstd::vector<std::unique_ptr<T>>
mit wahrscheinlich geringem bis keinem vergleichbaren Overhead verwenden können, aber diese Behauptung sollte getestet werden.Ungeachtet, niemals explizit Dinge in Ihrem Code frei . Packen Sie alles zusammen, um sicherzustellen, dass das Ressourcenmanagement automatisch erledigt wird. Sie sollten keine rohen Besitzzeiger in Ihrem Code haben.
Als Standard in einem Spiel würde ich wahrscheinlich mit gehen
std::vector<std::shared_ptr<T>>
. Wir erwarten sowieso, dass das Teilen schnell genug ist, bis die Profilerstellung etwas anderes sagt, es sicher ist und einfach zu verwenden ist.quelle
shared_ptr
einen benutzerdefinierten Smart Pointer mit einem speziellen Allokator in CPU / GPU-intensiven 3D-Anwendungen übertrifft. Bis Sie messen, wissen Sie nie ...Das Problem bei der Verwendung
vector<T*>
besteht darin, dass der Vektor immer dann bereinigt wird, wenn der Vektor unerwartet den Gültigkeitsbereich verlässt (z. B. wenn eine Ausnahme ausgelöst wird). Dadurch wird jedoch nur der Speicher freigegeben, den er für das Halten des Zeigers verwaltet , nicht der von Ihnen zugewiesene Speicher für was sich die Zeiger beziehen. Also GMansdelete_pointed_to
Funktion ist nur von begrenztem Wert, da es funktioniert nur , wenn nichts schief geht.Was Sie tun müssen, ist die Verwendung eines intelligenten Zeigers:
vector< std::tr1::shared_ptr<Enemy> > Enemies;
(Wenn Ihre Standardbibliothek ohne TR1 geliefert wird, verwenden Sie
boost::shared_ptr
stattdessen.) Mit Ausnahme sehr seltener Eckfälle (Zirkelverweise) werden die Probleme der Objektlebensdauer einfach beseitigt.Bearbeiten : Beachten Sie, dass GMan in seiner ausführlichen Antwort dies ebenfalls erwähnt.
quelle
delete_pointer_to
Möglichkeit nur erwähnt , ohne darauf einzugehen, da sie so viel minderwertiger ist. Ich hatte das Bedürfnis, die Standardlösung in eine kurze, einfache "Do-it-this-way" -Antwort umzuwandeln. (Die Zeigercontainer von Boost sind jedoch eine gute Alternative, und ich habe eine positive Bewertung abgegeben, weil ich sie erwähnt habe.) Es tut mir leid, wenn Sie sich falsch verstanden haben.:)
Ich gehe davon aus:
Folgende Dinge kommen mir in den Sinn:
quelle
Eine Sache, die sehr vorsichtig sein sollte, ist, WENN es zwei von Monster () ABGELEITETE Objekte gibt, deren Inhalt im Wert identisch ist. Angenommen, Sie möchten die DUPLICATE Monster-Objekte aus Ihrem Vektor entfernen (BASE-Klassenzeiger auf DERIVED Monster-Objekte). Wenn Sie die Standardsprache zum Entfernen von Duplikaten verwendet haben (sortieren, eindeutig, löschen: siehe LINK 2), treten Probleme mit Speicherverlusten und / oder doppelten Löschproblemen auf, die möglicherweise zu SEGMENTIERUNGSVERLETZUNGEN führen (ich habe diese Probleme persönlich gesehen) LINUX-Maschine).
Das Problem mit std :: unique () besteht darin, dass die Duplikate im Bereich [duplicatePosition, end) [einschließlich, exklusiv] am Ende des Vektors undefiniert sind als? Was passieren kann, ist, dass diese undefinierten ((?) Elemente möglicherweise ein zusätzliches Duplikat oder ein fehlendes Duplikat sind.
Das Problem ist, dass std :: unique () nicht darauf ausgerichtet ist, einen Zeigervektor richtig zu behandeln. Der Grund dafür ist, dass std :: unique-Kopien vom Ende des Vektors "abwärts" bis zum Anfang des Vektors eindeutig sind. Für einen Vektor von einfachen Objekten ruft dies den COPY CTOR auf, und wenn der COPY CTOR richtig geschrieben ist, gibt es kein Problem von Speicherlecks. Wenn es sich jedoch um einen Zeigervektor handelt, gibt es keinen anderen COPY CTOR als "bitweises Kopieren", sodass der Zeiger selbst einfach kopiert wird.
Es gibt andere Möglichkeiten, um diesen Speicherverlust zu beheben, als einen intelligenten Zeiger zu verwenden. Eine Möglichkeit, Ihre eigene leicht modifizierte Version von std :: unique () als "your_company :: unique ()" zu schreiben. Der grundlegende Trick besteht darin, dass Sie anstelle des Kopierens eines Elements zwei Elemente austauschen würden. Und Sie müssten sicher sein, dass Sie anstatt zwei Zeiger zu vergleichen, BinaryPredicate aufrufen, das den beiden Zeigern auf das Objekt selbst folgt, und den Inhalt dieser beiden von "Monster" abgeleiteten Objekte vergleichen.
1) @SEE_ALSO: http://www.cplusplus.com/reference/algorithm/unique/
2) @SEE_ALSO: Was ist der effizienteste Weg, um Duplikate zu löschen und einen Vektor zu sortieren?
Der zweite Link ist hervorragend geschrieben und funktioniert für einen std :: vector, hat jedoch Speicherlecks und doppelte Freigaben (die manchmal zu Verstößen gegen die SEGMENTIERUNG führen) für einen std :: vector
3) @SEE_ALSO: valgrind (1). Dieses "Memory Leak" -Tool unter LINUX ist erstaunlich, was es finden kann! Ich kann es nur wärmstens empfehlen!
Ich hoffe, in einem zukünftigen Beitrag eine schöne Version von "my_company :: unique ()" veröffentlichen zu können. Im Moment ist es nicht perfekt, da ich möchte, dass die 3-Argumente-Version mit BinaryPredicate nahtlos für einen Funktionszeiger oder einen FUNCTOR funktioniert, und ich habe einige Probleme, beide richtig zu handhaben. WENN ich diese Probleme nicht lösen kann, werde ich veröffentlichen, was ich habe, und die Community versuchen lassen, das zu verbessern, was ich bisher getan habe.
quelle
boost::smart_ptr
.