C ++: Intelligente Zeiger, unformatierte Zeiger, keine Zeiger? [geschlossen]

48

Welche Muster bevorzugen Sie bei der Entwicklung von Spielen in C ++ in Bezug auf die Verwendung von Zeigern (seien es keine, unformatiert, beschränkt, gemeinsam genutzt oder auf andere Weise zwischen intelligent und stumm)?

Sie könnten darüber nachdenken

  • Objektbesitz
  • Benutzerfreundlichkeit
  • Kopierrichtlinie
  • Overhead
  • zyklische Referenzen
  • Zielplattform
  • mit Behältern verwenden
jmp97
quelle

Antworten:

32

Nachdem ich verschiedene Ansätze ausprobiert habe, befinde ich mich heute im Einklang mit dem Google C ++ Style Guide :

Wenn Sie tatsächlich Zeigersemantik benötigen, ist scoped_ptr großartig. Sie sollten std :: tr1 :: shared_ptr nur unter ganz bestimmten Bedingungen verwenden, z. B. wenn Objekte in AWL-Containern gespeichert werden müssen. Sie sollten niemals auto_ptr verwenden. [...]

Im Allgemeinen ziehen wir es vor, Code mit klarem Objektbesitz zu entwerfen. Die klarste Objektzugehörigkeit wird erzielt, indem ein Objekt direkt als Feld oder lokale Variable verwendet wird, ohne dass Zeiger verwendet werden. [..]

Obwohl sie nicht empfohlen werden, sind Referenzzähler manchmal die einfachste und eleganteste Möglichkeit, ein Problem zu lösen.

jmp97
quelle
14
Heute möchten Sie möglicherweise std :: unique_ptr anstelle von scoped_ptr verwenden.
Klaim
24

Ich folge auch dem Gedankengang "starkes Eigentum". Ich möchte klar abgrenzen, dass "diese Klasse dieses Mitglied besitzt", wenn es angebracht ist.

Ich benutze selten shared_ptr. In diesem Fall verwende ich, weak_ptrwann immer ich kann , eine großzügige Verwendung, damit ich das Objekt wie ein Handle behandeln kann, anstatt die Referenzanzahl zu erhöhen.

Ich benutze scoped_ptrüberall. Es zeigt offensichtliche Eigenverantwortung. Der einzige Grund, warum ich solche Objekte nicht einfach zu Mitgliedern mache, ist, dass Sie sie weiterleiten können, wenn sie sich in einem scoped_ptr befinden.

Wenn ich eine Liste von Objekten brauche, benutze ich ptr_vector. Es ist effizienter und hat weniger Nebenwirkungen als die Verwendung vector<shared_ptr>. Ich denke, Sie können den Typ möglicherweise nicht im ptr_vector deklarieren (es ist eine Weile her), aber die Semantik macht es meiner Meinung nach wert. Grundsätzlich wird ein Objekt, das Sie aus der Liste entfernen, automatisch gelöscht. Dies zeigt auch offensichtliche Eigenverantwortung.

Wenn ich auf etwas verweisen möchte, versuche ich, es als Verweis anstelle eines nackten Zeigers zu verwenden. Manchmal ist dies nicht praktikabel (dh wenn Sie nach dem Erstellen des Objekts eine Referenz benötigen). In beiden Fällen zeigen Verweise offensichtlich, dass Sie das Objekt nicht besitzen, und wenn Sie die gemeinsame Zeigersemantik überall sonst befolgen, verursachen nackte Zeiger im Allgemeinen keine zusätzliche Verwirrung (insbesondere, wenn Sie eine Regel "Keine manuellen Löschvorgänge" befolgen). .

Mit dieser Methode konnte ein iPhone-Spiel, an dem ich gearbeitet habe, nur einen einzigen deleteAnruf tätigen, und zwar in der von mir geschriebenen Brücke zwischen Obj-C und C ++.

Generell bin ich der Meinung, dass Memory Management zu wichtig ist, um es den Menschen zu überlassen. Wenn Sie das Löschen automatisieren können, sollten Sie dies tun. Wenn der Overhead von shared_ptr zur Laufzeit zu teuer ist (vorausgesetzt, Sie haben die Threading-Unterstützung usw. deaktiviert), sollten Sie möglicherweise etwas anderes (z. B. ein Bucket-Muster) verwenden, um Ihre dynamischen Zuordnungen zu reduzieren.

Tetrad
quelle
1
Hervorragende Zusammenfassung. Meinen Sie eigentlich shared_ptr im Gegensatz zu Ihrer Erwähnung von smart_ptr?
jmp97
Ja, ich meinte shared_ptr. Ich werde das reparieren.
Tetrad
10

Verwenden Sie das richtige Werkzeug für den Job.

Wenn Ihr Programm Ausnahmen auslösen kann, stellen Sie sicher, dass Ihr Code ausnahmebewusst ist. Die Verwendung von intelligenten Zeigern, RAII und das Vermeiden von Zweiphasenkonstruktionen sind gute Ausgangspunkte.

Wenn Sie zyklische Referenzen ohne eindeutige Besitzersemantik haben, können Sie eine Garbage Collection-Bibliothek verwenden oder Ihr Design überarbeiten.

Gute Bibliotheken ermöglichen es Ihnen, das Konzept und nicht den Typ zu codieren, sodass es in den meisten Fällen keine Rolle spielt, welchen Zeigertyp Sie verwenden, und zwar über Ressourcenverwaltungsprobleme hinaus.

Wenn Sie in einer Umgebung mit mehreren Threads arbeiten, stellen Sie sicher, dass Sie wissen, ob Ihr Objekt möglicherweise von mehreren Threads gemeinsam genutzt wird. Einer der Hauptgründe für die Verwendung von boost :: shared_ptr oder std :: tr1 :: shared_ptr ist die Verwendung eines thread-sicheren Referenzzählers.

Wenn Sie sich Sorgen über die getrennte Zuordnung der Referenzzählungen machen, gibt es viele Möglichkeiten, dies zu umgehen. Mit der Bibliothek boost :: shared_ptr können Sie die Referenzzähler zusammenfassen oder boost :: make_shared (meine Präferenz) verwenden, um das Objekt und den Referenzzähler in einer einzigen Zuordnung zuzuordnen und so die meisten Bedenken hinsichtlich Cache-Fehlern zu beseitigen. Sie können den Leistungsverlust beim Aktualisieren der Referenzanzahl in leistungskritischem Code vermeiden, indem Sie einen Verweis auf das Objekt auf der obersten Ebene halten und direkte Verweise auf das Objekt weitergeben.

Wenn Sie den gemeinsamen Besitz benötigen, aber nicht die Kosten für Referenzzählung oder Speicherbereinigung bezahlen möchten, sollten Sie unveränderliche Objekte oder eine Kopie auf Schreibsprache verwenden.

Bedenken Sie, dass Ihre größten Leistungsgewinne bei weitem auf der Ebene der Architektur und anschließend auf der Ebene des Algorithmus liegen werden. Diese Bedenken auf niedriger Ebene sind zwar sehr wichtig, sollten aber erst angegangen werden, nachdem Sie die Hauptprobleme behoben haben. Wenn Sie mit Leistungsproblemen auf der Ebene von Cache-Fehlern zu tun haben, müssen Sie eine ganze Reihe von Problemen berücksichtigen, wie z. B. falsches Teilen, das nichts mit Zeigern zu tun hat.

Wenn Sie intelligente Zeiger verwenden, um Ressourcen wie Texturen oder Modelle gemeinsam zu nutzen, sollten Sie eine speziellere Bibliothek wie Boost.Flyweight in Betracht ziehen.

Sobald der neue Standard übernommen wurde, wird die Arbeit mit teuren Objekten und Containern durch Verschiebungssemantik, Wertverweise und perfekte Weiterleitung wesentlich einfacher und effizienter. Speichern Sie bis dahin keine Zeiger mit destruktiver Kopiersemantik wie auto_ptr oder unique_ptr in einem Container (Standardkonzept). Erwägen Sie die Verwendung der Boost.Pointer Container-Bibliothek oder das Speichern von Smart Pointern mit gemeinsamem Besitz in Containern. Bei leistungskritischem Code sollten Sie beide vermeiden, um aufdringliche Container wie die in Boost.Intrusive zu vermeiden.

Die Zielplattform sollte Ihre Entscheidung nicht zu sehr beeinflussen. Eingebettete Geräte, Smartphones, dumme Telefone, PCs und Konsolen können den Code problemlos ausführen. Projektanforderungen wie strenge Speicherbudgets oder keine dynamische Zuordnung je / nach dem Laden sind zutreffendere Bedenken und sollten Ihre Auswahl beeinflussen.

Jaeger
quelle
3
Die Ausnahmebehandlung auf den Konsolen kann ein bisschen zweifelhaft sein - insbesondere das XDK ist eine Art ausnahmefreundlich.
Crashworks
1
Die Zielplattform sollte Ihr Design wirklich beeinflussen. Die Hardware, die Ihre Daten transformiert, kann manchmal großen Einfluss auf Ihren Quellcode haben. Die PS3-Architektur ist ein konkretes Beispiel, bei dem Sie die Hardware für die Gestaltung Ihres Ressourcen- und Speichermanagements sowie Ihres Renderers wirklich benötigen.
Simon
Ich stimme dem nur geringfügig zu, insbesondere in Bezug auf GC. In den meisten Fällen sind zyklische Referenzen kein Problem für Schemata mit Referenzzählung. Im Allgemeinen treten diese zyklischen Besitzprobleme auf, weil die Leute nicht richtig über den Besitz von Objekten nachdenken. Nur weil ein Objekt auf etwas zeigen muss, heißt das nicht, dass es diesen Zeiger besitzen sollte. Das häufig zitierte Beispiel sind Back-Zeiger in Bäumen, aber das übergeordnete Element des Zeigers in einem Baum kann sicher ein Raw-Zeiger sein, ohne die Sicherheit zu beeinträchtigen.
Tim Seguine
4

Wenn Sie C ++ 0x verwenden, verwenden Sie std::unique_ptr<T>.

Es gibt keinen Performance-Overhead, im Gegensatz zu std::shared_ptr<T>denen , die den Overhead für die Referenzzählung haben. Ein unique_ptr besitzt seinen Zeiger, und Sie können den Besitz mit der Verschiebungssemantik von C ++ 0x übertragen . Sie können sie nicht kopieren, sondern nur verschieben.

Es kann auch in Containern verwendet werden, die z. B. std::vector<std::unique_ptr<T>>binärkompatibel und in der Leistung identisch sind std::vector<T*>, aber keinen Speicher verlieren, wenn Sie Elemente löschen oder den Vektor löschen. Dies hat auch eine bessere Kompatibilität mit STL-Algorithmen als ptr_vector.

IMO für viele Zwecke ist dies ein idealer Container: Direktzugriff, ausnahmesicher, verhindert Speicherlecks, geringer Overhead für die Vektorumverteilung (mischt nur um Zeiger hinter den Kulissen). Sehr nützlich für viele Zwecke.

AshleysBrain
quelle
3

Es empfiehlt sich, zu dokumentieren, welche Klassen welche Zeiger besitzen. Vorzugsweise verwenden Sie nur normale Objekte und keine Zeiger, wann immer Sie können.

Wenn Sie jedoch den Überblick über Ressourcen behalten müssen, ist die Übergabe von Zeigern die einzige Option. Es gibt einige Fälle:

  • Sie erhalten den Zeiger von einer anderen Stelle, verwalten ihn aber nicht: Verwenden Sie einfach einen normalen Zeiger und dokumentieren Sie ihn so, dass nach dem Versuch, ihn zu löschen, kein Codierer mehr vorhanden ist.
  • Sie erhalten den Zeiger von einer anderen Stelle und behalten den Überblick: Verwenden Sie scoped_ptr.
  • Sie erhalten den Zeiger von einer anderen Stelle und behalten ihn im Auge, benötigen jedoch eine spezielle Methode zum Löschen: Verwenden Sie shared_ptr mit einer benutzerdefinierten Löschmethode.
  • Sie benötigen den Zeiger in einem AWL-Container: Er wird kopiert, sodass Sie boost :: shared_ptr benötigen.
  • Viele Klassen teilen sich den Zeiger und es ist nicht klar, wer ihn löschen wird: shared_ptr (der obige Fall ist tatsächlich ein Sonderfall dieses Punktes).
  • Sie erstellen den Zeiger selbst und nur Sie benötigen ihn: Wenn Sie ein normales Objekt wirklich nicht verwenden können: scoped_ptr.
  • Sie erstellen den Zeiger und geben ihn für andere Klassen frei: shared_ptr.
  • Sie erstellen den Zeiger und übergeben ihn: Verwenden Sie einen normalen Zeiger und dokumentieren Sie Ihre Schnittstelle, damit der neue Eigentümer weiß, dass er die Ressource selbst verwalten soll!

Ich denke, das deckt ziemlich genau ab, wie ich meine Ressourcen im Moment verwalte. Die Speicherkosten eines Zeigers wie shared_ptr sind im Allgemeinen doppelt so hoch wie die Speicherkosten eines normalen Zeigers. Ich denke nicht, dass dieser Aufwand zu groß ist, aber wenn Sie wenig Ressourcen haben, sollten Sie überlegen, Ihr Spiel so zu gestalten, dass die Anzahl der intelligenten Zeiger verringert wird. In anderen Fällen entwerfe ich nur nach guten Prinzipien wie den obigen Aufzählungszeichen und der Profiler sagt mir, wo ich mehr Geschwindigkeit benötige.

Nef
quelle
1

Wenn es speziell um die Pointer von Boost geht, sollten sie meiner Meinung nach vermieden werden, solange ihre Implementierung nicht genau Ihren Anforderungen entspricht. Sie sind mit höheren Kosten verbunden, als man zunächst erwarten würde. Sie bieten eine Schnittstelle, über die Sie wichtige und wichtige Teile Ihres Speicher- und Ressourcenmanagements überspringen können.

Wenn es um Softwareentwicklung geht, denke ich, dass es wichtig ist, über Ihre Daten nachzudenken. Es ist sehr wichtig, wie Ihre Daten im Speicher dargestellt werden. Der Grund dafür ist, dass die CPU-Geschwindigkeit viel schneller gestiegen ist als die Speicherzugriffszeit. Dies macht die Speicher-Caches häufig zum Hauptengpass der meisten modernen Computerspiele. Durch die lineare Ausrichtung Ihrer Daten im Speicher gemäß der Zugriffsreihenfolge wird der Cache erheblich entlastet. Diese Art von Lösungen führt häufig zu saubereren Designs, einfacherem Code und definitiv zu Code, der leichter zu debuggen ist. Intelligente Zeiger führen leicht zu häufigen dynamischen Speicherzuweisungen von Ressourcen, wodurch sie über den gesamten Speicher verteilt werden.

Dies ist keine vorzeitige Optimierung, sondern eine gesunde Entscheidung, die so früh wie möglich getroffen werden kann und sollte. Es ist eine Frage des architektonischen Verständnisses der Hardware, auf der Ihre Software ausgeführt wird, und es ist wichtig.

Bearbeiten: Es gibt ein paar Dinge, die in Bezug auf die Leistung von geteilten Zeigern beachtet werden müssen:

  • Der Referenzzähler ist Heap zugeordnet.
  • Wenn Sie Thread-Sicherheit aktiviert verwenden, erfolgt die Referenzzählung über verriegelte Vorgänge.
  • Durch Übergeben des Zeigers als Wert wird die Referenzanzahl geändert. Dies bedeutet, dass Operationen mit gegenseitiger Sperre höchstwahrscheinlich mit wahlfreiem Zugriff im Speicher ausgeführt werden (Sperren + wahrscheinlicher Cache-Fehler).
Simon
quelle
2
Sie haben mich um jeden Preis gemieden. Anschließend beschreiben Sie eine Art von Optimierung, die für Spiele in der realen Welt selten von Belang ist. Die meiste Spieleentwicklung ist durch Entwicklungsprobleme (Verzögerungen, Fehler, Spielbarkeit usw.) gekennzeichnet, nicht durch einen Mangel an CPU-Cache-Leistung. Aus diesem Grund bin ich nicht der Meinung, dass dieser Rat keine vorzeitige Optimierung darstellt.
Kevin42
2
Ich muss mit der frühen Gestaltung des Datenlayouts einverstanden sein. Es ist wichtig, dass die Leistung einer modernen Konsole / eines modernen Mobilgeräts nicht zu kurz kommt.
Olly
1
Dies ist ein Problem, das ich in einem der AAA-Studios gesehen habe, in denen ich gearbeitet habe. Sie können sich auch den Head Architect von Insomniac Games, Mike Acton, anhören. Ich sage nicht, dass Boost eine schlechte Bibliothek ist, es ist nicht nur gut für leistungsstarke Spiele geeignet.
Simon
1
@ kevin42: Cache-Kohärenz ist heute wahrscheinlich die Hauptquelle für Optimierungen auf niedriger Ebene in der Spieleentwicklung. @Simon: Die meisten shared_ptr-Implementierungen vermeiden Sperren auf jeder Plattform, die Compare-and-Swap unterstützt, einschließlich Linux- und Windows-PCs und meiner Meinung nach auch der Xbox.
1
@Joe Wreschnig: Das stimmt, der Cache-Miss ist immer noch am wahrscheinlichsten, obwohl er eine Initialisierung eines gemeinsamen Zeigers verursacht (kopieren, aus einem schwachen Zeiger erstellen usw.). Ein L2-Cache-Miss auf modernen PCs entspricht 200 Zyklen und auf der PPC (xbox360 / ps3) ist er höher. In einem intensiven Spiel können Sie bis zu 1000 Spielobjekte haben, da jedes Spielobjekt über eine Reihe von Ressourcen verfügt. Wir beschäftigen uns mit Problemen, bei denen die Skalierung von großer Bedeutung ist. Dies wird wahrscheinlich am Ende eines Entwicklungszyklus Probleme verursachen (wenn Sie die hohe Anzahl von Spielobjekten treffen).
Simon
0

Ich neige dazu, überall intelligente Zeiger zu verwenden. Ich bin mir nicht sicher, ob dies eine wirklich gute Idee ist, aber ich bin faul und sehe keinen wirklichen Nachteil [außer wenn ich eine C-Zeiger-Arithmetik machen wollte]. Ich benutze boost :: shared_ptr, weil ich weiß, dass ich es kopieren kann - wenn zwei Entitäten ein Bild teilen, sollte eines sterben und das andere das Bild nicht verlieren.

Der Nachteil dabei ist, dass ein Objekt nicht gelöscht wird, wenn es etwas löscht, auf das es zeigt und das es besitzt, aber auch etwas anderes darauf zeigt.

Die kommunistische Ente
quelle
1
Ich habe share_ptr auch fast überall verwendet - aber heute versuche ich zu überlegen, ob ich für ein Datenelement tatsächlich eine gemeinsame Eigentümerschaft benötige oder nicht. Ist dies nicht der Fall, ist es möglicherweise sinnvoll, diese Daten zu einem Nicht-Zeiger-Member für die übergeordnete Datenstruktur zu machen. Ich finde, dass klares Eigentum das Design vereinfacht.
jmp97
0

Die Vorteile der Speicherverwaltung und der Dokumentation durch gute Smart Pointer bedeuten, dass ich sie regelmäßig verwende. Wenn der Profiler jedoch aufruft und mir mitteilt, dass mich eine bestimmte Nutzung kostet, greife ich auf die neolithischere Zeigerverwaltung zurück.

Tenpn
quelle
0

Ich bin alt, oldskool und ein Fahrradzähler. In meiner eigenen Arbeit verwende ich rohe Zeiger und keine dynamischen Zuweisungen zur Laufzeit (mit Ausnahme der Pools selbst). Alles ist gepoolt und das Eigentum ist sehr streng und niemals übertragbar. Wenn es wirklich gebraucht wird, schreibe ich einen benutzerdefinierten kleinen Block-Allokator. Ich stelle sicher, dass es während des Spiels einen Zustand gibt, in dem sich jeder Pool selbst löschen kann. Wenn die Dinge haarig werden, wickle ich Objekte in Griffe, damit ich sie verschieben kann, aber ich möchte lieber nicht. Behälter sind benutzerdefinierte und extrem nackte Knochen. Ich verwende auch keinen Code mehr.
Obwohl ich niemals die Tugend all der intelligenten Zeiger und Container und Iteratoren und so weiter bestreiten würde, bin ich dafür bekannt, dass ich in der Lage bin, extrem schnell (und einigermaßen zuverlässig) zu codieren. wie Herzinfarkte und ewige Alpträume).

Bei der Arbeit ist natürlich alles anders, es sei denn, ich erstelle Prototypen, die ich zum Glück viel tun darf.

Kaj
quelle
0

Fast keine, obwohl dies zugegebenermaßen eine seltsame Antwort ist und wahrscheinlich bei weitem nicht für alle geeignet ist.

In meinem persönlichen Fall hat es sich jedoch als sehr viel nützlicher erwiesen, alle Instanzen eines bestimmten Typs in einer zentralen Sequenz mit wahlfreiem Zugriff (threadsicher) zu speichern und stattdessen mit 32-Bit-Indizes (relative Adressen, z. B.) zu arbeiten. eher als absolute Zeiger.

Für den Anfang:

  1. Es halbiert den Speicherbedarf des analogen Zeigers auf 64-Bit-Plattformen. Bisher habe ich noch nie mehr als 4,29 Milliarden Instanzen eines bestimmten Datentyps benötigt.
  2. Es stellt sicher, dass alle Instanzen eines bestimmten Typs Tniemals zu stark im Speicher verstreut sind. Dadurch werden Cache-Ausfälle für alle Arten von Zugriffsmustern verringert, selbst wenn verknüpfte Strukturen wie Bäume durchlaufen werden, wenn die Knoten nicht mit Zeigern, sondern mit Indizes verknüpft sind.
  3. Parallele Daten lassen sich leicht mit billigen parallelen Arrays (oder spärlichen Arrays) anstelle von Bäumen oder Hash-Tabellen verknüpfen.
  4. Gesetzte Schnittpunkte können in linearer Zeit oder besser mit einem parallelen Bitsatz gefunden werden.
  5. Wir können die Indizes radixsortieren und ein sehr cachefreundliches sequentielles Zugriffsmuster erhalten.
  6. Wir können nachverfolgen, wie viele Instanzen eines bestimmten Datentyps zugewiesen wurden.
  7. Minimiert die Anzahl der Stellen, die sich mit Ausnahmesicherheit befassen müssen, wenn Sie sich für solche Dinge interessieren.

Das heißt, Bequemlichkeit ist ein Nachteil sowie die Typensicherheit. Wir können keine Instanz zugreifen , Tohne Zugang zu beiden Behältern und Index. Und ein einfaches Altes int32_tsagt uns nichts darüber, auf welchen Datentyp es sich bezieht, daher gibt es keine Typensicherheit. Wir könnten versehentlich versuchen, Barüber einen Index auf eine zuzugreifen Foo. Um das zweite Problem zu lindern, mache ich oft so etwas:

struct FooIndex
{
    int32_t index;
};

Das scheint albern, gibt mir aber die Typensicherheit zurück, damit die Leute nicht versehentlich versuchen können Bar, Fooohne einen Compilerfehler auf einen Index zuzugreifen . Der Einfachheit halber akzeptiere ich nur die leichten Unannehmlichkeiten.

Eine andere Sache, die für die Leute eine große Unannehmlichkeit sein könnte, ist, dass ich keinen vererbungsbasierten OOP-Polymorphismus verwenden kann, da dies einen Basiszeiger erfordern würde, der auf alle Arten von unterschiedlichen Untertypen mit unterschiedlichen Größen- und Ausrichtungsanforderungen zeigen kann. Aber ich verwende heutzutage nicht viel Vererbung - bevorzuge den ECS-Ansatz.

Was shared_ptrversuche ich es nicht so viel zu verwenden. Die meiste Zeit finde ich es nicht sinnvoll, das Eigentum zu teilen, und dies kann willkürlich zu logischen Lecks führen. Zumindest auf hohem Niveau gehört eines oft zu einer Sache. Ich fand es oft verlockend, shared_ptrdie Lebensdauer eines Objekts an Orten zu verlängern, die sich nicht wirklich mit Eigentum befassten, wie zum Beispiel bei einer lokalen Funktion in einem Thread, um sicherzustellen, dass das Objekt nicht zerstört wird, bevor der Thread beendet ist es benutzen.

Um dieses Problem anzugehen, bevor shared_ptrich oder GC oder ähnliches verwende, bevorzuge ich häufig kurzlebige Aufgaben, die aus einem Thread-Pool ausgeführt werden. Wenn dieser Thread die Zerstörung eines Objekts anfordert, wird die tatsächliche Zerstörung auf einen Safe verschoben Zeitpunkt, zu dem das System sicherstellen kann, dass kein Thread auf diesen Objekttyp zugreifen muss.

Manchmal verwende ich immer noch Ref-Counting, behandle es aber wie eine Strategie des letzten Auswegs. Und es gibt ein paar Fälle, in denen es wirklich Sinn macht, Eigentümer zu sein, wie die Implementierung einer beständigen Datenstruktur, und ich finde, dass es durchaus Sinn macht, shared_ptrsofort danach zu greifen .

Trotzdem verwende ich meistens Indizes und verwende sowohl rohe als auch intelligente Zeiger sparsam. Ich mag Indizes und die Arten von Türen, die sich öffnen, wenn Sie wissen, dass Ihre Objekte zusammenhängend gespeichert und nicht über den Speicherbereich verstreut sind.

user77245
quelle