Lösungen für den dynamischen Versand von nicht verwandten Typen

8

Ich untersuche mögliche Implementierungen der dynamischen Versendung von nicht verwandten Typen in modernem C ++ (C ++ 11 / C ++ 14).

Unter "dynamische Verteilung von Typen" verstehe ich einen Fall, bei dem wir in der Laufzeit einen Typ aus der Liste nach seinem ganzzahligen Index auswählen und etwas damit tun müssen (eine statische Methode aufrufen, eine Typeigenschaft verwenden usw.) / p>

Betrachten Sie zum Beispiel den Strom serialisierter Daten: Es gibt verschiedene Arten von Datenwerten, die unterschiedlich serialisiert / deserialisiert werden; Es gibt mehrere Codecs, die Serialisierung / Deserialisierung durchführen; und unser Code liest den Typmarker aus dem Stream und entscheidet dann, welchen Codec er aufrufen soll, um den vollen Wert zu lesen.

Ich bin an einem Fall interessiert, in dem es viele Operationen gibt, die für Typen aufgerufen werden können (verschiedene statische Methoden, Typeigenschaften ...), und wo unterschiedliche Zuordnungen von logischen Typen zu C ++ - Klassen möglich sind und nicht nur 1: 1 (im Beispiel mit Serialisierung bedeutet dies, dass es mehrere Datenarten geben könnte, die alle mit demselben Codec serialisiert sind).

Ich möchte auch die manuelle Codewiederholung vermeiden und den Code leichter wartbar und weniger fehleranfällig machen. Leistung ist auch sehr wichtig.

Momentan sehe ich diese möglichen Implementierungen, vermisse ich etwas? Kann das besser gemacht werden?

  1. Schreiben Sie manuell so viele Funktionen mit switch-case, wie es mögliche Operationen für Typen gibt.

    %Vor%

Vorteile : Es ist einfach und verständlich; Der Compiler kann Inline-Methoden senden.

Nachteile : Es erfordert viel manuelle Wiederholung (oder Code-Generierung durch externes Tool).

  1. Erstellen Sie Dispatching-Tabelle wie folgt

    %Vor%

Vorteile : Es ist ein bisschen flexibler - man muss eine Dispatcher-Klasse für jeden versendeten Typ schreiben, aber dann könnte man sie in verschiedenen Dispatch-Tabellen kombinieren.

Nachteile : Es erfordert viel Dispatching-Code zu schreiben. Und es gibt einige Overhead aufgrund der virtuellen Dispatching und Unmöglichkeit, Inline-Codec-Methoden in die Website des Aufrufers.

  1. Verwenden Sie die Template-Dispatching-Funktion

    %Vor%

Vorteile : Es erfordert nur eine Switch-Case-Dispatching-Funktion und ein wenig Code in jedem Operationsaufruf (zumindest manuell geschrieben). Und der Compiler kann das, was er findet, inline einfügen.

Nachteile : Es ist komplizierter, erfordert C ++ 14 (um so sauber und kompakt zu sein) und beruht auf der Compiler-Fähigkeit, um nicht verwendete Codec-Instanzen zu optimieren (die nur dazu verwendet werden, die richtige Überladung zu wählen) Codec).

  1. Wenn für eine Gruppe von logischen Typen mehrere Implementierungsklassen zugeordnet werden können (Codecs in diesem Beispiel), ist es möglicherweise besser, die Lösung # 3 zu verallgemeinern und vollständig generische Dispatch-Funktionen zu schreiben, die eine Kompilierungszeit zwischen Typwerten erhalten und aufgerufene Typen. Etwas wie das:

    %Vor%

Ich stehe auf Lösung # 3 (oder # 4). Aber ich frage mich - ist es möglich, das manuelle Schreiben von dispatch function zu vermeiden? Sein Schaltkasten meine ich. Dieser switch-case wird vollständig aus der Kompilierungszeit-Zuordnung zwischen Typenwerten und Typen abgeleitet. Gibt es eine Methode, um die Generierung für den Compiler zu verarbeiten?

    
Alexander Morozov 07.10.2016, 11:20
quelle

2 Antworten

0

Hier ist eine Lösung irgendwo zwischen Ihren # 3 und # 4. Vielleicht gibt es Inspiration, nicht sicher, ob es wirklich nützlich ist.

Anstatt eine Interface-Basisklasse und einen virtuellen Versand zu verwenden, können Sie einfach Ihren "Codec" -Code in einige nicht verwandte Merkmalstrukturen einfügen:

%Vor%

Sie können dann diese Merkmalstypen in eine Typenliste einfügen, hier verwende ich einfach ein std::tuple dafür:

%Vor%

Jetzt können wir eine generische Dispatch-Funktion schreiben, die den n-ten Typ an einen gegebenen Funktor weitergibt:

%Vor%

Dies verwendet eine lineare Suche, um das richtige Merkmal zu finden, aber mit etwas Mühe könnte man es zumindest zu einer binären Suche machen. Außerdem sollte der Compiler in der Lage sein, den gesamten Code zu inline zu schreiben, da kein virtueller Versand involviert ist. Vielleicht ist der Compiler sogar schlau genug, um daraus einen Switch zu machen.

Live-Beispiel: Ссылка

    
Horstling 07.10.2016, 17:27
quelle
3

Tag-Dispatching, bei dem Sie einen Typ übergeben, um eine Überladung auszuwählen, ist effizient. std -Bibliotheken verwenden es normalerweise für Algorithmen in Iteratoren, sodass verschiedene Iteratorkategorien unterschiedliche Implementierungen erhalten.

Wenn ich eine Liste von Typ-IDs habe, stelle ich sicher, dass sie zusammenhängend sind und schreibe eine Sprungtabelle.

Dies ist ein Array von Zeigern auf Funktionen, die die Aufgabe erledigen.

Sie können das Schreiben in C ++ 11 oder besser automatisieren; Ich nenne es den magischen Schalter , da es sich wie ein Laufzeitschalter verhält und eine Funktion aufruft, deren Wert auf der Kompilierzeit basiert aus der Laufzeit eins. Ich mache die Funktionen mit lambdas und erweitere ein Parameterpaket in ihnen, damit sich ihre Körper unterscheiden. Sie senden dann an das übergebene Funktionsobjekt.

Schreiben Sie das, dann können Sie Ihren Serialisierungs- / Deserialisierungscode in "typsicheren" Code kopieren. Verwenden Sie Merkmale, um aus Kompilierzeitindizes Tags zu typisieren und / oder basierend auf dem Index zu einer überladenen Funktion zu versenden.

Hier ist ein C ++ 14 magischer Schalter:

%Vor%

Verwendung sieht wie folgt aus:

%Vor%

Live-Beispiel .

Wenn Sie Ihre Typ-Enumeration zum Kompilieren zur Typ-Map registrieren können (über Typ-Traits oder was auch immer), können Sie einen magischen Schalter durchlaufen, um Ihren Runtime-Enum-Wert in ein Kompilierungs-Typ-Tag umzuwandeln.

%Vor%

Dann kannst du deine serialize / deserialize wie folgt schreiben:

%Vor%

Wenn wir enum DataType haben, schreiben wir eine Eigenschaft:

%Vor%

all das Heavy Lifting wird jetzt von enum_to_type traignes Klassenspezialisierungen, DataType enum und Überladungen des Formulars erledigt:

%Vor%

welche typsicher sind.

Beachten Sie, dass Ihr any nicht wirklich ein any , sondern ein variant ist. Es enthält eine begrenzte Liste von Typen, nichts.

Dieser magic_switch wird verwendet, um std::visit function neu zu implementieren, was Ihnen auch einen typsicheren Zugriff auf den in variant gespeicherten Typ ermöglicht.

Wenn Sie möchten, dass es irgendetwas enthält, müssen Sie bestimmen, welche Operationen Sie unterstützen möchten, Schreib-Lösch-Code schreiben, der beim Speichern in any ausgeführt wird Typ-gelöschte Operationen entlang der Daten, und Bob ist dein Onkel.

    
Yakk 07.10.2016 11:51
quelle