Wie kann ich Speicher entwerfen, der der Standard-Implementierung von std :: any entspricht?

8

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.

    
user2296177 07.05.2016, 23:30
quelle

3 Antworten

0
  

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 :

%Vor%

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:

  1. nothrow_move_constructible
  2. %Code%. In meinem Fall ist das sizeof(T) <= sizeof(storage)
  3. %Code%. In meinem Fall ist das 3 * sizeof(void*)
quelle
0

Normalerweise nimmt any alles und weist ihm dynamisch ein neues Objekt zu:

%Vor%

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:

%Vor%

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:

%Vor%

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 :

%Vor%

wo unsere VTable -Typen etwas wie:

sind %Vor%

und dann verwenden wir einfach diese vtable, um unsere verschiedenen Operationen zu implementieren. Wie:

%Vor%     
Barry 08.05.2016 14:53
quelle
-1

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)

%Vor%

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.

    
bolov 08.05.2016 00:58
quelle

Tags und Links