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:
- 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.
- Es stellt sicher, dass alle Instanzen eines bestimmten Typs
T
niemals 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.
- Parallele Daten lassen sich leicht mit billigen parallelen Arrays (oder spärlichen Arrays) anstelle von Bäumen oder Hash-Tabellen verknüpfen.
- Gesetzte Schnittpunkte können in linearer Zeit oder besser mit einem parallelen Bitsatz gefunden werden.
- Wir können die Indizes radixsortieren und ein sehr cachefreundliches sequentielles Zugriffsmuster erhalten.
- Wir können nachverfolgen, wie viele Instanzen eines bestimmten Datentyps zugewiesen wurden.
- 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 , T
ohne Zugang zu beiden Behältern und Index. Und ein einfaches Altes int32_t
sagt 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
, Foo
ohne 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_ptr
versuche 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_ptr
die 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_ptr
ich 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_ptr
sofort 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.