Verwenden Sie boost::variant
oder eine der vielen Open-Source C ++ 11-Implementierungen einer "Tagged Union" oder Variante in Ihren C ++-Projekten?
boost::variant
ist eine großartige Bibliothek. Ich habe strict_variant
erstellt, um ein paar Dinge über boost::variant
anzusprechen, die mir nicht gefallen hat.
Die TL; DR -Version ist, dass strict_variant
im Gegensatz zu boost::variant
oder std::variant
niemals eine Ausnahme ausgelöst oder eine dynamische Zuweisung vornehmen wird, um Typen zu unterstützen, die Bewegungen haben. Die Standardversion wird einfach eine statische Behauptung nicht bestehen, wenn dies geschehen würde. Das strict_variant::easy_variant
wird in dieser Situation Zuteilungen durchführen, sodass Sie sich dafür entscheiden können, wenn Sie möchten, und diese beiden Versionen der Variante "gut spielen" zusammen. Diese Art von Dingen ist oft ein großes Problem bei Projekten mit Echtzeitanforderungen oder in eingebetteten Geräten, die diese C ++ - Funktionen möglicherweise nicht zulassen oder einfach nicht haben. Wenn Sie eine Bibliothek erstellen, die möglicherweise in "herkömmlichen" Projekten verwendet wird, die die Benutzerfreundlichkeit haben, die von boost::variant
stammt, aber möglicherweise auch in Projekten mit restriktiven Anforderungen verwendet wird, und Sie einen variant
verwenden möchten Im Rahmen der API könnte strict_variant
eine Möglichkeit bieten, alle glücklich zu machen.
Abgesehen davon gibt es einige Probleme in der Schnittstelle der Variante, die es angesprochen haben, die tägliche IMHO angenehmer zu verwenden. (Dies war eigentlich die ursprüngliche Motivation des Projekts.)
Ich mochte diesen Code nicht, dass dieser Code ohne Warn- oder Fehlermeldungen kompiliert wurde:
boost::variant<std::string, int > v;
v = true ;
Normalerweise würde meine variant
einschränkender darüber, welche impliziten Conversions passieren können.
Ich wollte , dass solche Dinge kompilieren und tun sollten, was Sinn macht, selbst wenn eine Überlastungsauflösung mehrdeutig wäre.
variant< bool , long , double , std::string> v;
v = true ; // selects bool
v = 10 ; // selects long
v = 20 . 5f ; // selects double
v = " foo " ; // selects string
Ich wollte auch, dass ein solches Verhalten (was in solchen Fällen ausgewählt wird) tragbar ist.
(Für Codebeispiele wie diese, in denen boost::variant
unglückliches Verhalten hat, siehe "Zusammenfassung und Motivation" in der Dokumentation.)
In strict_variant
ändern wir die Überlastauflösung in diesen Situationen, indem wir einige Kandidaten entfernen.
Zum Beispiel:
bool
, Integral, Floating Point, Zeiger, Charakter und einigen anderen Klassen.int -> long
und int -> long long
sind Kandidaten, die long long
wird eliminiert.Weitere Informationen finden Sie unter Dokumentation.
Ich mochte nicht, dass dieser boost::variant
stillschweigend Backup -Kopien meiner Objekte erstellt. Betrachten Sie beispielsweise dieses einfache Programm, in dem A
und B
definiert wurden, um alle CTOR- und DTOR -Anrufe zu protokollieren.
int main () {
using var_t = boost::variant<A, B>;
var_t v{ A ()};
std::cout << " 1 " << std::endl;
v = B ();
std::cout << " 2 " << std::endl;
v = A ();
std::cout << " 3 " << std::endl;
}
Die boost::variant
erzeugt die folgende Ausgabe:
A ()
A(A&&)
~A()
1
B()
B(B&&)
A( const A &)
~A()
B( const B &)
~A()
~B()
~B()
2
A()
A(A&&)
B( const B &)
~B()
A( const A &)
~B()
~A()
~A()
3
~A()
Dies mag für einige Programmierer ziemlich überraschend sein.
Wenn Sie dagegen die C ++ 17 std::variant
oder eine der Varianten mit "manchmal leerer" Semantik verwenden, erhalten Sie so etwas (diese Ausgabe von std::experimental::variant
)
A ()
A(A&&)
~A()
1
B()
~A()
B(B&&)
~B()
2
A()
~B()
A(A&&)
~A()
3
~A()
Dies ist viel näher an dem, was der naive Programmierer erwartet, der nicht über interne Details von boost::variant
weiß - die einzigen Kopien seiner Objekte, die existieren, sind das, was er in seinem Quellcode sehen kann.
Diese Art von Dingen spielt normalerweise keine Rolle, aber manchmal, wenn Sie beispielsweise ein böses Gedächtnisverfälschungsproblem debuggen (vielleicht gibt es einen schlechten Code in einem der in der Variante enthaltenen Objekte), dann können diese zusätzlichen Objekte, Bewegungen und Kopien können Machen Sie die Dinge übrigens komplizierter.
Hier ist, was Sie mit strict_variant
bekommen:
A ()
A(A&&)
~A()
1
B()
B(B&&)
~A()
~B()
2
A()
A(A&&)
~B()
~A()
3
~A()
Doch strict_variant
hat keinen leeren Zustand und ist voll und ganz ausnahmslos sicher!
(Diese Beispiele aus gcc 5.4
siehe Code im example
.)
Um die Unterschiede zusammenzufassen:
std::variant
ist selten leer, immer stackbasiert. Tatsächlich ist es genau leer, wenn eine Ausnahme ausgelöst wird. Später wirft es verschiedene Ausnahmen aus, wenn Sie versuchen zu besuchen, wenn es leer ist.boost::variant
ist nie leer, normalerweise stapelbasiert. Es muss eine dynamische Zuweisung und eine Sicherungskopie durchführen, wenn eine Ausnahme ausgelöst werden könnte , aber das wird direkt danach befreit, wenn eine Ausnahme nicht tatsächlich ausgelöst wird.strict_variant
ist nie leer und stapelbasiert genau dann, wenn der aktuelle Wertschöpfungs-Typ nichts bewegt ist. Es macht nie eine Backup -Bewegung oder eine Kopie und macht nie eine Ausnahme. Jeder Ansatz hat seine Verdienste. Ich habe den strict_variant
-Ansatz gewählt, weil ich ihn einfacher finde und es vermeidet, was ich für Nachteile von boost::variant
und std::variant
betrachte. Und wenn Sie es schaffen, alle Ihre Typen zu konstruktibel, was ich oft kann, dann bietet Ihnen strict_variant
eine optimale Leistung, die gleiche wie std::variant
, ohne einen leeren Zustand.
Eine eingehende Diskussion des Designs finden Sie in der Dokumentation.
Für ein sanftes Intro in Varianten und einen Überblick über strikte Variante finden Sie Folien aus einem Vortrag, das ich darüber gegeben habe: [PPTX] [PDF]
Auf Github -Seiten.
strict_variant
zielt auf den C ++ - 11 -Standard ab.
Es ist bekannt, dass es mit gcc >= 4.8
und clang >= 3.5
funktioniert und gegen MSVC 2015
getestet wird.
strict_variant
kann als IS in Projekten verwendet werden, die -fno-exceptions
und -fno-rtti
erfordern.
Die strenge Variante ist im Rahmen der Boost -Software -Lizenz verfügbar.