Der Standardarbeitsentwurf (n4582, 20.6.3, S.552) enthält den folgenden Vorschlag für Implementierungen von std::any
:
Implementierungen sollten die Verwendung von dynamisch zugewiesenem Speicher für ein kleines enthaltenes Objekt vermeiden. [Beispiel: wo das konstruierte Objekt nur einen int enthält. -end-Beispiel] Eine solche Small-Object-Optimierung soll nur auf Typen T angewendet werden, für die is_nothrow_move_constructible_v wahr ist.
Soweit ich weiß, kann std::any
leicht durch Typ-Löschen / virtuelle Funktionen und dynamisch zugewiesenen Speicher implementiert werden.
Wie kann std::any
die dynamische Zuweisung vermeiden und trotzdem solche Werte zerstören, wenn zum Zeitpunkt der Zerstörung keine Informationen zur Kompilierzeit bekannt sind; Wie würde eine Lösung aussehen, die dem Standardvorschlag entspricht?
Wenn jemand eine mögliche Implementierung des nicht-dynamischen Teils sehen möchte, habe ich eine auf Code Review geschrieben: Ссылка
Es ist ein bisschen zu lang für eine Antwort hier. Es basiert auf den Vorschlägen von Kerrek SB auf den Kommentaren unten.
Wie kann std :: any die dynamische Zuordnung vermeiden und trotzdem solche zerstören? Werte, wenn zum Zeitpunkt des Zeitpunkts keine Informationen zur Kompilierzeit bekannt sind Zerstörung
Das scheint eine geladene Frage zu sein. Der letzte Entwurf benötigt diesen Konstruktor:
%Vor%Ich kann mir nicht vorstellen, warum Sie "Typ löschen" müssen, es sei denn, Sie möchten, dass Code sowohl kleine als auch große Fälle gleichzeitig behandelt. Aber warum nicht so etwas? 1
%Vor%Im ersten Fall können Sie einen Zeiger auf Ihren nicht initialisierten Speicher haben:
%Vor%Verwenden Sie eine Union als @ KerrekSB vorgeschlagen.
Beachten Sie, dass der Typ für die Speicherklasse nicht bekannt sein muss. Mit einer Art von Handle / Dispatch (nicht sicher über den wirklichen Namen des Idioms) System wird es an dieser Stelle trivial.
Zuerst wollen wir uns ansehen, wie die Zerstörung aussehen würde:
%Vor% Dann die Klasse any
:
Hier wird die Logik herausgefiltert, die den Kompilierungs-Typ für ein Handler / Dispatch-System kennt, während der Großteil der any
-Klasse nur mit RTTI umgehen muss.
1 : Hier sind die Bedingungen, nach denen ich suchen würde:
nothrow_move_constructible
sizeof(T) <= sizeof(storage)
3 * sizeof(void*)
Normalerweise nimmt any
alles und weist ihm dynamisch ein neues Objekt zu:
Wir nutzen die Tatsache, dass placeholder
polymorph ist, um all unsere Operationen - Vernichtung, Besetzung usw. - zu handhaben. Aber jetzt wollen wir die Allokation vermeiden, was bedeutet, dass wir all die schönen Dinge vermeiden, die Polymorphismus uns gibt - und müssen reimplementieren sie. Am Anfang haben wir eine Vereinigung:
wo wir template <class T> is_small_object { ... }
haben, um zu entscheiden, ob wir ptr = new holder<T>(value)
oder new (&buffer) T(value)
machen. Aber wir müssen nicht nur konstruieren - wir müssen auch Vernichtung und Typ-Info-Retrieval durchführen, die je nachdem, in welchem Fall wir uns befinden, anders aussehen. Entweder machen wir delete ptr
oder wir machen static_cast<T*>(&buffer)->~T();
, letzteres hängt davon ab, T
! zu verfolgen!
Also stellen wir unser eigenes vtable-ähnliches Ding vor. Unser any
wird dann festhalten:
Sie könnten stattdessen einen neuen Funktionszeiger für jedes Op erstellen, aber es gibt wahrscheinlich mehrere andere Ops, die ich hier vermisse (zB OP_CLONE
, was dazu führen könnte, dass das übergebene Argument geändert wird als union
...) und du willst deine any
size nicht einfach mit einer Reihe von Funktionszeigern aufblasen. Auf diese Weise verlieren wir ein kleines bisschen Leistung im Austausch für einen großen Unterschied in der Größe.
Bei der Konstruktion füllen wir dann sowohl storage
als auch vtable
:
wo unsere VTable
-Typen etwas wie:
und dann verwenden wir einfach diese vtable, um unsere verschiedenen Operationen zu implementieren. Wie:
%Vor% Inspiriert von boost any habe ich dieses (teste es auf ideone) (Ich habe einen minimalen Fall erstellt, um zu zeigen, wie man einen Typ gelöschten Container wie any
ohne dynamischen Speicher zerstört. Ich habe mich nur auf Konstruktor / Destruktor konzentriert , alles andere auslassen, Bewegungssemantik und andere Dinge ignorieren)
Die Ausgabe:
%Vor% Natürlich sind die ersten Provisorien zerstört, die letzten beiden beweisen, dass die Zerstörung von Any
den richtigen Destruktor aufruft.
Der Trick ist Polymorphie zu haben. Deshalb haben wir Base_holder
und Holder
. Wir initialisieren sie über die Platzierung neu in std::aligned_storage
und rufen den Destruktor explizit auf.
Dies ist nur ein Beweis dafür, dass Sie den richtigen Destruktor aufrufen können, ohne den Typ zu kennen, der von Any
gehalten wird. Natürlich hätten Sie in einer realen Implementierung eine Union für diesen oder einen Zeiger auf einen dynamisch zugewiesenen Speicher und einen Booleschen, der Ihnen sagt, welchen Sie haben.