2016-07-06 7 views
1

Ich muss Protokollierung zu einem Legacy-C++ - Projekt hinzufügen, die Hunderte von benutzerdefinierten Strukturen/Klassen enthält. Diese Strukturen enthalten nur primäre Typen wie int, float, char[], enum.Wie loggen benutzerdefinierte POD-Struktur in C++

Der Inhalt von Objekten muss protokolliert werden, vorzugsweise in lesbarer Form, aber nicht zwingend, solange das Objekt rekonstruiert werden kann.

Anstatt verschiedene Serialisierungsmethoden für jede Klasse zu schreiben, gibt es eine alternative Methode?

+1

Wenn es keine Zeiger sind und die Typen sind triviale kopierbar, können Sie ' schreibe und lese sie. Nicht für Menschen lesbar, aber das ist das Beste, was Sie bekommen können. Wenn es Zeiger gibt, kein Glück. –

+0

Reflektion ist mit C++ möglich, nicht einfach aber möglich http://stackoverflow.com/a/11748131/5076707 – Pumkko

+0

sind die Mitglieder öffentlich? –

Antwort

0

Was Sie wollen, ist ein Program Transformation System (PTS). Dies sind Werkzeuge, die Quellcode lesen, Compiler-Datenstrukturen (normalerweise ASTs) erstellen können, die den Quellcode darstellen, und Ihnen erlauben, die ASTs zu modifizieren und den Quellcode aus dem modifizierten AST zu regenerieren.

Diese sind nützlich, weil sie die Sprache "verlassen" und somit keine sprachlichen Beschränkungen für die Analyse oder Transformation haben. Es ist also egal, ob deine Sprache nicht für alles Reflexion hat; Eine gute PTS gibt Ihnen vollen Zugriff auf alle Detail der Sprache, einschließlich solcher Arcana als Kommentare und Radix auf numerische Literale.

Einige PTSes sind spezifisch für eine Zielsprache (z. B. "Jackpot" ist nur für Java geeignet). Ein wirklich guter PTS ist eine Beschreibung einer beliebigen Programmiersprache und kann diese Sprache dann manipulieren. Diese Beschreibung muss es dem PTS ermöglichen, den Code zu analysieren, ihn zu analysieren (zumindest Symboltabellen zu erstellen) und das geparste/modifizierte Ergebnis zu drucken.

Mit guten PTSes können Sie die Änderungen schreiben, die Sie mit Source-to-Source-Transformationen vornehmen möchten.Dies sind Regeln für das Änderung geschrieben in etwa folgenden Form:

if you see *this*, replace it by *that* when *condition* 

wo diese und dass sind Muster mit der Syntax der Zielsprache verarbeitet werden, und Zustand sind ein Prädikat (Test) Das muss wahr sein, damit die Regel angewendet werden kann. Die Muster stellen wohlgeformte Codefragmen dar und ermöglichen typischerweise, dass Metavariablen Platzhalter für beliebige Subfragmente darstellen.

Sie können PTSes für eine Vielzahl von Programmmanipulationsaufgaben verwenden. Für OPs Fall möchte er alle Strukturen im Programm aufzählen, die interessierende Teilmenge auswählen und dann für jede ausgewählte Struktur einen Serializer als eine Modifikation des ursprünglichen Programms erzeugen.

Um für diese bestimmte Aufgabe praktisch zu sein, muss der PTS in der Lage sein, zu analysieren und Namen aufzulösen (Symboltabellen erstellen) C++. Es gibt nur sehr wenige Tools, die dies tun können: Clang, unser DMS Software Reengineering Toolkit und der Rose Compiler.

Eine Lösung DMS verwendet sieht dies so etwas wie:

domain Cpp~GCC5; -- specify the language and specific dialect to process 

pattern log_members(m: member_declarations): statements = TAG; 
     -- declares a marker we can place on a subtree of struct member declarations 

rule serialize_typedef_struct(s: statement, m: member_declarations, i: identifier): 
      statements->statements 
    = "typedef struct { \m } \i;" -> 
    "typedef struct { \m } \i; 
     void \make_derived_name\(serialize,\i) (*\i argument, s: stream) 
      { s << "logging" << \toString\(\i\); 
      \log_members\(\m\) 
      }" 
     if selected(i); -- make sure we want to serialize this one 

rule generate_member_log_list(m: member_declarations, t: type_specification, n: identifier): statements -> statements 
    " \log_members\(\t \n; \m\)" -> " s << \n; \log_members\(\m\) "; 

rule generate_member_log_base(t: type_specification, n: identifier): statements -> statements 
    " \log_members\(\t \n; \)" -> " s << \n; "; 

ruleset generate_logging { 
    serialize_typedef struct, 
    generate_member_log_list, 
    generate_member_log_base 
} 

Die Domain Erklärung DMS, die spezifische Sprache Front-End zu verwenden erzählt. Ja, GCC5 als Dialekt unterscheidet sich von VisualStudio2013 und DMS kann beides.

Die Musterlog_members wird als eine Art transformierende Zeiger verwendet, sich daran zu erinnern, dass es einige Arbeit zu tun ist. Es umschließt eine Sequenz der Struktur member_declarations als eine Agenda (Tag). Was die Regeln tun, markiert zuerst interessierende Strukturen mit log_members, um die Notwendigkeit zu ermitteln, den Protokollierungscode zu generieren, und dann die Protokollierungsaktionen des Mitglieds zu generieren. Das log_members Muster fungiert als eine Liste; es wird jeweils ein Element verarbeitet, bis ein endgültiges Element verarbeitet ist, und dann verschwindet das Zeichen log_members, nachdem es seinen Zweck erfüllt hat.

Die Regelserialize_typedef_struct wird im Wesentlichen verwendet, um den Code zu scannen nach geeigneten Strukturen suchen zu serialisiert. Wenn es eine typedef für eine Struktur findet, prüft es, dass struct eine OP ist, die serialisiert werden möchte (andernfalls kann man wenn bedingt verlassen). Die Meta-Funktion selected ist benutzerdefiniert (hier nicht gezeigt), um die Namen von Strukturen von Interesse zu erkennen. Wenn eine geeignete typedef-Anweisung gefunden wird, wird sie durch typedef ersetzt (wodurch sie beibehalten wird) und durch die Shell einer Serialisierungsroutine, die das Tagesordnungelement log_members enthält und die gesamte Liste der Mitglieder der Struktur enthält. (Wenn der Code Strukturen auf eine andere Weise deklariert, z. B. als Klasse, benötigen Sie zusätzliche Regeln, um die Syntax dieser Fälle zu erkennen). Die Verarbeitung des Tagesordnungspunkts durch wiederholtes Umschreiben erzeugt die Protokollaktionen für die einzelnen Mitglieder.

Die Regeln sind in DMS-Regel-Syntax geschrieben; Die C++ - Muster werden in metaquotes"..." geschrieben, damit DMS die Regelsyntax von der C++ - Syntax unterscheiden kann.Platzhaltervariablen v werden im Regelheader gemäß ihren syntaktischen Kategorien deklariert und in den Meta-Anführungszeichen mit einer Escape-Notation \ v angezeigt. [Beachten Sie die Unescaped i im ausgewählten Funktionsaufruf: es ist nicht innerhalb Metaquotes]. In ähnlicher Weise werden Metafunktionen und Musterreferenzen innerhalb der Metaquoten in ähnlicher Weise maskiert, so dass sie anfangs ungerade aussahen, einschließlich des Namens des entwichenen Musters und der entdeckten Meta-Klammern.

Die beiden Regeln generate_member_log_xxx übergeben Sie die allgemeinen und letzten Fälle der Protokollgenerierung. Der allgemeine Fall behandelt ein Mitglied mit mehr Mitgliedern zu tun; Der letzte Fall behandelt das letzte Mitglied. (Eine geringfügige Variante wäre die Verarbeitung einer leeren Mitgliederliste durch Umschreiben in die triviale Nullanweisung ;). Dies ist im Wesentlichen eine Liste hinunter, bis Sie vom Ende fallen. Wir "schummeln" und schreiben einen ziemlich einfachen Logging-Code, der auf das Überladen von Stream-Schreibvorgängen zaehlt, um mit den verschiedenen Datentypen umzugehen, die OP behauptet. Wenn er komplexere Typen aufweist, die eine spezielle Behandlung erfordern (z. B. Zeiger auf ...), möchte er möglicherweise spezialisierte Regeln schreiben, die diese Fälle erkennen und anderen Code erzeugen.

Die rulesetgenerate_logging Pakete, die diese Regeln bis zu einem ordentlichen Bündel. Sie können DMS gelegentlich bitten, diesen Regelsatz für ganze Dateien auszuführen und Regeln anzuwenden, bis keine Regeln mehr angewendet werden können. Die serialize_typef_structure Regel findet die Strukturen von Interesse, die Serialisierungsfunktionshülle und die log_members Tagesordnungspunkt generieren, die wiederholt neu geschrieben werden, um die Serialisierung der Mitglieder zu erstellen.

Dies ist die Grundidee. Ich habe diesen Code nicht getestet, und es gibt normalerweise einige überraschende Syntaxvariationen, mit denen Sie fertig werden müssen, was bedeutet, dass Sie ein paar mehr Regeln in derselben Zeile schreiben müssen.

Aber einmal implementiert, können Sie diese Regel über den Code ausführen, um serialisierte Ergebnisse zu erhalten. (Man könnte selected implementieren, um benannte Strukturen abzulehnen, die bereits eine Serialisierungsroutine haben, oder alternativ Regeln hinzufügen, die jeden existierenden Serialisierungscode durch neu erzeugten Code ersetzen, wobei sichergestellt wird, dass die Serialisierungsverfahren immer der Strukturdefinition entsprechen). Es gibt die offensichtliche Erweiterung zum Erzeugen eines serialisierten Strukturlesers.

Sie können diese gleichen Ideen mit Clang und/oder der Rose Compiler wohl implementieren. Allerdings bieten diese Systeme keine Quell-zu-Quell-Umschreibungsregeln, so dass Sie prozeduralen Code schreiben müssen, um Bäume hoch und runter zu klettern, einzelne Knoten zu inspizieren usw. Es ist IMHO viel mehr Arbeit und viel weniger lesbar.

Und wenn Sie laufen in der nächsten „C++ reflektieren nicht, dass“ können Sie das Problem mit dem gleichen Werkzeug angehen: -}

+0

Danke für die lange Antwort, sehr interessante Möglichkeit, das Problem zu lösen. – Guangyu

0

Da C++ keine Spiegelung enthält, können Sie die Elemente eines Objekts zur Laufzeit nicht dynamisch überprüfen. Daraus folgt, dass Sie für jeden Typ eine spezifische Serialisierungs-/Streaming-/Protokollierungsfunktion schreiben müssen.

Wenn alle verschiedenen Typen Mitglieder mit demselben Namen hätten, könnten Sie eine Template-Funktion schreiben, um damit umzugehen, aber ich nehme an, dass das nicht der Fall ist.

0

Da C++ keine Reflexion hat, ist das nicht so einfach. Wenn Sie eine ausführliche Lösung vermeiden möchten, können Sie eine variadische Vorlage verwenden.

z. `Klasse MyStruct { privat: int a; float f;

öffentlich: void log() { log_fields (a, f); } }; `

wobei log_fields() die Variadic-Vorlage ist. Es müsste für alle grundlegenden Typen, die auf diesen benutzerdefinierten Typen gefunden werden, und auch für einen rekursiven Fall spezialisiert werden.

+0

Unter Verwendung dieses Ansatzes muss OP für jede seiner Hunderten von Strukturen immer noch eine log() - Definition enthalten. –