12 Stimmen

Faule Parameterauswertung

Ich füge ein bisschen Tracing- und Debugging-Code in eine Klasse ein, die ich gerade überarbeite.

Ich habe eine Trace Objekt, das über einige Filtereigenschaften und -methoden verfügt bool CanTrace(Level, , TracePropertyList = no_additional_properties) y bool Trace(Level, string, TracePropertyList = no_additional_properties) .

Es gibt bereits viele Stellen im Code, an denen dieses Trace-Objekt verwendet wird, und das String-Argument der Trace Methode ist in der Regel ein Ausdruck, den ich nicht auswerten möchte, wenn ich nicht am Ende Informationen zur Ablaufverfolgung ausgeben möchte.

Wiederholung des Codestücks

if(trace.CanTrace(LEVEL_INFO, some_props))
  trace.Trace(LEVEL_INFO, consume_time().to_str(), some_props);

ist hässlich, und ich hätte gern etwas Kürzeres.

Ich habe über die Makros nachgedacht

#define TRACE_WITH_PROPS(LEVEL,STRING,PROPS) //...

y

#define TRACE(LEVEL,STRING) //...

Gibt es eine bessere Möglichkeit, dies zu tun? Möglicherweise mit Vorlagen oder C++11? Ich mag es nicht, Dinge mit Defines vor dem Compiler zu verstecken, und ich tue mein Bestes, um einige Makros an anderer Stelle in dieser Codebasis zu entfernen.

6voto

Jan Hudec Punkte 69126

In C++11 können Sie Closure verwenden. Sie würden so etwas haben wie:

trace.Trace(LEVEL_INFO, [&](){ return format("x is %i") % ExpensiveWayToGetX(y, z); }, some_props);

In C++03 können Sie den boost.lambda-Hack für einen ähnlichen Effekt verwenden.

Ein Makro, das die if(trace.CanTrace(...)) trace.Trace(...) ist immer noch etwas effizienter, da das Objekt nicht einmal mit allen Referenzen initialisiert wird, die die Schließung benötigt, wenn die Ablaufverfolgung nicht eingeschaltet ist. Ich schlage vor, das Makro mit der Stream-Schnittstelle zu kombinieren, so dass Sie

#define TRACE(level, some_props) \
    if(!trace.CanTrace(level, some_props)) 0; \
        else trace.Trace(level, some_props)

und rufen Sie es auf wie

TRACE(LEVEL_INFO, some_props) << "x is " << ExpensiveWayToGetX(y, z);

oder definieren operator() anstelle von operator<< und für die printf-ähnliche Formatierung:

TRACE(LEVEL_INFO, some_props)("x is %i", ExpensiveWayToGetX(y, z));

Die wenn no aktiviert, als 0, sonst ist tatsächlich Trace da, damit es nicht die else frisst, wenn Sie jemals schreiben:

if(IsSomethingWrong())
    TRACE(LEVEL_WARNING, some_props) << WhatIsWrong() << " is wrong";
else
    DoSomething();

(ohne else im Makro würde das else danach zum if innerhalb des Makros gehen, aber mit dem else wird es korrekt analysiert)

0voto

Bjarke H. Roune Punkte 3537

Um die Verarbeitung von Strings zu vermeiden, sind Lambdas eine gute Lösung, wie in der Antwort von Johannes Schaub. Wenn Ihr Compiler noch keine Lambdas unterstützt, könnten Sie es so machen, ohne C++0x-Funktionen und ohne Makros:

doTrace(LEVEL_INFO, some_props) << "Value of i is " << i;

doTrace würde ein temporäres Objekt mit einem virtuellen Operator<< zurückgeben. Wenn kein Tracing durchgeführt werden soll, dann wird ein Objekt zurückgegeben, dessen Operator<< nichts tut. Andernfalls wird ein Objekt zurückgegeben, dessen Operator<< das Gewünschte bewirkt. Der Destruktor des temporären Objekts kann signalisieren, dass die auszugebende Zeichenkette fertig ist. Wenn also die abschließende Verarbeitung des Trace-Ereignisses eine Ausnahme auslösen kann, müssen Sie möglicherweise eine Überladung von operator<< am Ende des Ereignisses einfügen

Diese Lösung verursacht mehrere virtuelle Funktionsaufrufe, auch wenn kein Tracing durchgeführt wird, und ist daher weniger effizient als "if(tracing) ...". Das sollte aber nur in leistungskritischen Schleifen eine Rolle spielen, in denen Sie wahrscheinlich ohnehin kein Tracing durchführen wollen. In diesen Fällen oder wenn die Logik für die Durchführung der Ablaufverfolgung komplizierter ist, als dass sie bequem in eine Folge von <<'s passt, kann man auf jeden Fall zur Überprüfung der Ablaufverfolgung mit einem if zurückkehren.

0voto

Ajay Punkte 17373

Ich würde mich auf jeden Fall dafür verbürgen, dass ein gut definierter Satz von Makros besser ist als derselbe Code, der zig Mal erscheint. Definieren Sie einfach eine Reihe von Makros mit mehreren Namen, mit verschiedenen Ebenen - und darüber hinaus ein Makro, das Folgendes enthält if-else , template magic , assertions , static-asserts , static-type-checking , mehr von macro-abuse und so weiter.

Die Verwendung von Makros würde es Ihnen auch ermöglichen, bestimmte Teile bedingt einzuschließen/auszuschließen (z. B. Ausschluss von statischen Asserts, Asserts usw.). Anfangs wäre es schwierig, Makros zu schreiben, sich an sie anzupassen und Compiler-/Laufzeitfehler im Zusammenhang mit ihnen zu finden, aber in späteren Phasen der Kodierung, Fehlersuche und Entwicklung werden sie mit den funktionsreichen Möglichkeiten, die sie bieten, in Windeseile verschwinden.

Zeichnen Sie Makros sorgfältig!

0voto

towi Punkte 21168

Soweit ich weiß, sind Template-Funktionen der beste Ort, um sicherzustellen, dass der Compiler versucht, Sachen weg zu optimieren. Ich würde also hoffen, dass etwas wie das Folgende den Compiler dazu bringen könnte, die Erstellung der message heraus, wenn Sie die Trace_<false> trace;

Das sollte genügen und Ihnen eine Vorstellung vermitteln:

template<bool yesno> struct Trace_ {};
template<> struct Trace_<false> {
    void operator()(const string &) const {}
};
template<> struct Trace_<true> {
   void operator()(const string &message) const {
      clog << message << endl;
   }
};

//Trace_<true> trace;
Trace_<false> trace;

int main() {
    trace("a message");
}

Irgendwie denke ich, dass in Instanz erstellen trace ist nicht die beste Idee. Vielleicht kann man dies nur mit freien Funktions-Templates oder einer statischen Member-Funktion ( operator() nicht statisch sein kann)?

0voto

towi Punkte 21168

(da diese Antwort einen völlig anderen Ansatz verfolgt, trenne ich sie von meiner anderen Antwort, bei der ich die Frage missverstanden habe)

Unter Laufzeit Sie wollen verhindern, dass ein big-string-merge Ausdruck ausgeführt wird? Hmm ... schwierig. Ich denke, Sie könnten verwenden perfekte Weiterleitung von variablen Vorlagenargumenten nur als eine grobe Idee (C++0x'ish Pseudocode):

template<typename MSGS...>
void trace(const MSGS... &&msgs) {
    if(IS_TRACE) {
        doTrace( std::forward<MSGS...>(msgs) );
    }
}

void func() {
    trace("A String", 52, image, matrix, "whatver doTrace can handle");
}

doTrace kann dann eine rekursive variadic-template Funktion (wie die printf in Bjarne Stroutrups FAQs:Variadic Templates ). Die Website && y forward sollten sicherstellen, dass die Argumente nicht berührt werden, bis sie bei doTrace . Sie sollten also nur einen Aufruf mit ein paar Argumenten und die if(IS_TRACE) -Test, wenn Sie nichts verfolgen wollen.

Wenn Ihnen die Idee gefällt, werde ich sie programmieren - rufen Sie einfach an!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X