SucheBlog abonnierenVerwaltung des BlogsKategorienKontaktMarkus Brückner |
Mittwoch, 27. April 2005Template-Spielereien die ZweiteNachdem ich mich letztens über die netten Spielereien, die man mit Templates so veranstalten kann, ausgelassen habe, hat mich Q auf die libdebug hingewiesen. Nachdem ich mir das ein klein wenig angeschaut habe, ist mir aufgefallen, daß man die Spielereien eigentlich mal so aufbohren könnte, daß sie als Logging-Framework nutzbar sind. Beibehalten wollte ich die Stream-artige Verwendung und die Elimination von Code in der Releaseversion. Zusätzlich sollten Loglevel hinzukommen. Nach einigem Hin und Her habe ich mich für folgendes grobes Interface entschieden:
Logger logger(std::cout, TRACE); // erstelle einen Logger, der nach std::cout loggt und
// alle Message mit Level TRACE und höher schreibt
logger << TRACE << "Nachricht 1" << endl; // gibt Nachricht 1 mit Level TRACE aus
logger << INFO << "Nachricht 2" << endl; // gib Nachricht 2 mit Level INFO aus
Im obigen Beispiel sollte in der Releaseversion aller Code, welcher Nachrichten mit Level TRACE oder DEBUG ausgibt rausfliegen. Da die einzelnen Operatoren allerdings in einzelne Funktionsaufrufe gewandelt werden, muss man an der Stelle einen Status während des Kompilierens mitschleppen, damit der Compiler weiß, welcher Level zuletzt gesetzt wurde. Daß ist normalerweise so einfach nicht möglich, da man dem Compiler keine Variablen und Logik zuweisen kann, die er zur Compilezeit ausführen soll. Dank der Operatorüberladung ist etwas ähnliches allerdings trotzdem möglich. Grundsätzlich genügt es für die Aufgabe, die Codestücke in zwei Klassen zu teilen: solche, die DEBUG oder TRACE enthalten und solche, die das nicht tun. Erstere müssen in der Releaseversion wegoptimiert werden können, letztere sollen stehenbleiben. Um nun den Compiler entscheiden zu lassen, mit welcher Art von Code er es zu tun hat, bieten sich Typen an. Analysieren wir folgende Zeile: logger << TRACE << "Blubber"; Diese läßt sich gedanklich umformen zu: operator<<(operator<<(logger, TRACE), "Blubber"); Wir sehen also zwei operator<<-Aufrufe mit verschiedenen Parametern. Der innere (der mit TRACE) bekommt den Logger und TRACE als Parameter. Hier soll das TRACE entscheiden, daß dieser Aufruf letztlich wegoptimiert wird. Dafür definieren wir uns ein template TTraceStream<bool>, welches durch die Spezialisierung des Templateparameters die bekannte Unterscheidung zwischen Release- und Debugversion trifft. Immer wenn der Compiler auf diesen Typ trifft, soll er abhängig vom Templateparameter diese Entscheidung treffen. Nun brauchen wir noch einen Typ für die Klasse von Ausgaben, die immer im Programm enthalten sein sollen (INFO und wichtiger). Dieser heißt TLogStream und ist von TTraceStream Fehlt also nur noch die initiale Entscheidung für einen der Typen durch das Loglevel. Auch hier kommt uns wieder das Typsystem zu Hilfe. Wir teilen nämlich ebenso die Loglevel in zwei Klassen: TraceLevel (ein enum, der TRACE und DEBUG enthält) und LogLevel (ein enum, der INFO und andere enthält). Nun können wir als Einstiegspunkt zwei Operatoren definieren (TraceStream steht hier aus Platzgründen für die aktuelle Instanziierung von TTraceStream<bool>): TraceStream &operator<<(TLogStream &, const TraceLevel); TLogStream &operator<<(TraceStream &, const LogLevel); Was passiert hier? Die erste Form des Operators wird vom Compiler immer dann gewählt, wenn er ein Objekt vom Typ TLogStream erhält und auf dieses ein TraceLevel ausgeben soll. Damit muss er quasi in den Trace-Modus "umschalten". Das tut er, indem er ein Objekt vom Typ TraceLevel zurückliefert (korrekterweise liefert er eine Referenz auf das übergebene TLogStream-Objekt zurück, welches nach TraceStream gecastet wurde. Daher auch die Ableitung, damit der Cast funktioniert). Der umgekehrte Fall wird vom zweiten Operator gelöst: er liefert ein Objekt vom Typ TLogStream zurück, wenn er einen TraceStream erhält und dorthin ein LogLevel ausgeben soll. Damit der Upcast (TLogStream wurde ja von TraceStream abgeleitet) funktioniert, muss natürlich der ursprünglich angelegte Logger vom Typ TLogStream sein. Die restlichen Sachen sind eher einfach. Wir definieren ein paar Ausgabeoperatoren für verschiedene Sachen jeweils zweimal. Immer eine Version für den TLogStream und eine für den TTraceStream<bool>. Diese Operatoren liefern jeweils den übergebenen Steamtyp zurück um so in dem "Modus" zu bleiben. Die Definition der Operatoren für TTraceStream<bool> macht dabei wieder Gebrauch von der Templatespezialisierung um dem Compiler eine Wegoptimierung im Releasebinary zu ermöglichen. Da der Compiler für die Auswahl der überladenen Operatoren immer den Typ nimmt, der am genauesten zu der Parameterliste passt, können wir so durch den Typ des Rückgabewertes der Operatoren den Status über das gewählte Loglevel bereits zur Compilezeit durch die einzelnen Statements ziehen. Das ganze funktioniert natürlich nur innerhalb eines Statements. Der ursprüngliche Logger muss ja des Upcasts wegen vom Typ TLogStream sein. Daher beginnt das Spiel mit den Typein jedem neuen Statement von vorn. Aus diesem Grund MUSS der Loglevel auch immer der erste Wert sein, den man in einem Debugstatement ausgibt. Folgender Code würde beispielsweise nicht tun, was man erwartet: logger << DEBUG; logger << "Blubber"; Die Debugversion würde hier tatsächlich noch funktionieren. Zuerst wird der Loglevel auf DEBUG gesetzt (das eigentliche Setzen des Levels der Nachricht geschieht zu Laufzeit. Zur Compilezeit wird nur anhand des Typs gearbeitet) und dann wird die entsprechende Nachricht ausgegeben. Kompiliert man dieses Programm nun allerdings als Releaseversion, dann passiert etwas merkwürdiges. Die Zeile mit DEBUG wird durch die Optimierung gänzlich wegfallen. Da allerdings logger vom Typ TLogStream ist, wird in der nächsten Zeile wieder der Operator für die Ausgabe auf einem TLogStream gewählt. Dieser wird aber nie wegoptimiert werden, weswegen plötzlich die Nachricht "Blubber" mit dem zuletzt gesetzten Debuglevel ausgegeben wird. Das ist aber keineswegs das, was der Programmierer wollte. Damit nun nicht jeder anhand der paar Informationen in dem Artikel die Sache nachprogrammieren muss, habe ich das bereits fix und fertig in ein Headerfile gepackt, welches man hier herunterladen kann. Der Code ist getestet mit GCC 3.3, Visual C++ 2003 und dem Comeau C++ Compiler. Alle kompilieren das ganze ohne Warnungen und zumindest bei GCC und VC++ funktioniert der Code auch wie geplant (von Comeau habe ich den Onlinecompiler zum testen verwendet, kann also keinen Code ausführen). Zur Verwendung in größeren Programmen müßte man das ganze evtl. noch threadsafe machen. Das ist eine kleinere Übung, da man dazu nur eine Handvoll Funktionen synchronisieren muss (setLevel, setLastLevel, put und modify). Für den portablen Proof-of-Concept habe ich darauf verzichtet. Das Headerfile ist im Kopf nocheinmal umfassend dokumentiert, einschließlich Beispiel. Der Code ist public domain, kann also absolut frei verwendet werden. Download der logging.hxx Freitag, 22. April 2005Template-SpielereienIch hatte heute nachmittag etwas Langeweile (hach je, wenn das mein Betreuer liest...) und habe mit den Template-Fähigkeiten von C++ rumgespielt. Herausgekommen ist dabei eine rudimentäre Debug-Klasse, mit der man recht bequem Debugging-Code in ein C++-Programm einfügen kann, der sich auch quasi "auf Knopfdruck" ausschalten läßt. Das ganze basiert auf der Spezialisierung eines Templates mit bool als Parametertyp. Das allgemeine Template stellt die gesamte Funktionalität zur Ausgabe von Debugging-Informationen zur Verfügung. Die Spezialisierung für den Wert false enthält jeweils nur leere Funktionen. Nun wird abhängig von der Definition der Präprozessordirektive NDEBUG das Template entweder mit true oder false instanziiert. Die über das Programm verstreuten Debugsausgaben gehen also einmal gegen tatsächliche Ausgabefunktionen und beim anderen mal an leere Funktionsrümpfe. Schaltet man nun bei aktuellen Compilern die Optimierungen ein (bei GCC reicht schon -O), dann werden die leeren Funktionsrümpfe wegoptimiert und die gesamten Debug-Ausgaben entfallen im Kompilat. Somit hat man eine praktische Möglichkeit, ohne wilde Präprozessororgien die beim Programmieren verwendeten Tracing- und Debuggingausgaben aus der Releaseversion zu entfernen. Das Interface ist ein wenig nach den IOStreams modelliert, so daß der Krempel bequem zu verwenden ist. Da ich den Code nicht für besonders patentwürdig halte und wahrscheinlich nicht der erste bin, der auf so eine Idee kommt, ist der quasi public domain. Macht damit, was ihr wollt. debugstream.hxx
#ifndef DEBUGSTREAM_HXX
#define DEBUGSTREAM_HXX
#include <iostream>
namespace Debug
{
struct Tendl
{
};
static const Tendl endl = Tendl();
template <bool> class TDebugStream
{
std::ostream &output_stream;
public:
TDebugStream(std::ostream &os)
: output_stream(os)
{
};
TDebugStream &operator<<(const Tendl &)
{
output_stream << std::endl;
return *this;
};
template <class T> TDebugStream &operator<<(const T &t)
{
output_stream << t;
return *this;
};
};
template <> class TDebugStream<false>
{
public:
TDebugStream(std::ostream &os)
{
};
TDebugStream &operator<<(const Tendl &)
{
return *this;
};
template <class T> TDebugStream &operator<<(const T &t)
{
return *this;
};
};
#ifdef NDEBUG
typedef TDebugStream<false> DebugStream;
#else
typedef TDebugStream<true> DebugStream;
#endif
};
#endif
Verwendet wird das ganze dann wie folgt (DebugStream, der auf std::cout landet):
#include <iostream>
#include "debugstream.hxx"
int main()
{
Debug::DebugStream ds(std::cout);
ds << "Ein Test-Text mit einer Zahl:" << 10 << Debug::endl;
return 0;
};
Abhängig von der Definition von NDEBUG wird das Programm nun entweder "Ein Test-Text mit einer Zahl:10" ausgeben oder schweigen. Wenn man das mit Optimierungen kompiliert, dann wird im Fall von NDEBUG im Kompilat keine Referenz zu DebugStream vorhanden sein. Donnerstag, 14. April 2005Subversion und Code 502 (Bad Gateway)Letztens habe ich ja mal eine Anleitung gepostet, wie man einem Apache 2 mehrere name based virtual hosts beibiegt und dabei SSL verwenden kann. Ich habe da ja schon geschrieben, daß das eine schlechte Idee ist und nur unter ganz bestimmten Umständen sinnvoll sein kann. Nachdem ich mich natürlich nicht an meine eigene Warnung gehalten habe, begrüßt mich heute das von mit verwendete Subversion mit einem freundlichen svn: COPY of ...: 502 Bad Gateway (https://xyz). Ah ja... Etwas Recherche brachte diesen Artikel zu Tage, welcher das Problem beschreibt. Konkret fällt WebDAV unter SSL dann auf die Nase, wenn man eine URL versucht mit einem COPY-Request anzufassen, welche sich unterhalb eines mit SSL gesicherten name based virtual host befindet. Warum das so ist, weiß ich im Moment auch nicht. Es hilft in jedem Fall, den Servername des default-HTTPS-Hosts (bei Debian in der Datei /etc/apache2/sites-available/ssl-secured) auf den Namen des Repository-Hosts zu setzen und dort auch die entsprechende Location für das WebDAV zu konfigurieren. Verrückt.
(Seite 1 von 1, insgesamt 3 Einträge)
|