SucheBlog abonnierenVerwaltung des BlogsKategorienKontaktMarkus Brückner |
Mittwoch, 4. Januar 2006Zu lange arbeiten schadetGrad im ICQ: ******** 16:45:51: puuh, jetzt hab ich mich aber selber verarscht Hihi... P.S: Weil ich weiß, daß er/sie/es es liest: es ist im allgemeinen keine Gute Idee (TM) schlüsselwörter von C++ in C zu verwenden... Mittwoch, 21. Dezember 2005A-Gen 0.1.2 released
Vorhin eben habe ich mal wieder ein paar kleinere Probleme an A-Gen behoben und eine neue Version veröffentlicht. Jetzt sollte das Package wieder unter Debian unstable und Ubuntu installieren und der Code mit GCC 4.x kompilieren. Damit ist Version 0.1 soweit am Ende ihrer Entwicklung. Welche Features Version 0.2 genau bieten wird, weiß ich noch nicht. Auf jeden Fall soll die Möglichkeit zum automatischen Aufsplitten der Index-Seite kommen.
Sonntag, 18. Dezember 2005Template-Spielereien die x-teDie schon vor längerer Zeit mal vorgestellten Templatespielereien sind irgendwann tatsächlich mal in ein paar Projekten benutzt worden. Aus verschiedenen Gründen kam plötzlich die Anforderung: "Mach den Logger doch mal bitte thread-safe!". Äh, ja... Konkret sollte der Ausdruck logger << LEVEL_DEBUG << "Eine Debug-Nachricht." << endl; nicht unterbrechbar sein, damit sich in der Ausgabe keine Nachrichten überlappen. Das ist nicht ganz so trivial, wie es scheint, denn in Wirklichkeit ist das nicht ein einziger Ausdruck, sondern dort stehen insgesamt 3 mehr oder minder unabhängige Teilausdrücke. Formt man dieses Zeile in die klassische Aufrufform um, so sieht das so aus (mal die Version der freien Operatoren, die nicht Element eines Typs sind, verwendet): operator<<(operator<<(operator<<(logger, LEVEL_DEBUG), "Eine Debug-Nachricht."), endl);. Man sieht also, daß hier dreimal ein operator<< aufgerufen wird, dessen Rückgabewert als Parameter für weitere Aufrufe verwendet wird. Um nun die gesamte Zeile thread-safe zu bekommen, muss der innerste Aufruf (welcher zuerst ausgeführt wird) einen Mutex locken und der äußerste muss ihn freigeben. Ersteres ist einfach: da das erste Element einer Zeile immer ein Level sein soll, kann man das locken des Mutex in diesen verlagern. Die zweite Anforderung ist schon schwieriger. Es ist für einen Operator nicht feststellbar, daß er der äußerste (und damit letzte einer Zeile) ist. Nun könnte man festlegen, daß jede Zeile mit endl angeschlossen werden muss und dann bei Empfang dieses Modifiers den Mutex wieder freigeben. Aus demselben Grund, wieso ein Operator nicht feststellen kann, daß er der letzte ist, kann man allerdings die Regel mit dem endl nicht erzwingen. Es ist zwar möglich, zu verhindern, daß auf ein endl noch etwas folgt, allerdings kann nicht erzwungen werden, daß überhaupt eines auftauchen muss. Um nun zu vermeiden, daß sich der Programmierer um diese internen Details kümmern muss, kann man die Lebenszeitregeln von C++-Objekten nutzen. Dazu modifiziert man die Ausgabeoperatoren so, daß der Typ von logger direkt nur noch Loglevel-Typen als Parameter akzeptiert. So erzwingt man, daß das erste einer Zeile ein Level ist. Der Ausgabeoperator verpackt den übergebenen Logger dann in ein temporäres Objekt und liefert dieses zurück. Außerdem lockt er den Mutex. Die folgenden Ausgabeoperatoren nehmen als ersten Parameter dieses temporäre Objekt, führen ihre Ausgabeoperation auf den enthaltenen Logger aus und liefern das Objekt wieder zurück. Auch der letzte Operator in der Zeile liefert dieses Objekt zurück. Da es von niemandem mehr entgegen genommen wird, wird es vernichtet. Dabei wird sein Destruktor aufgerufen, welcher den gelockten Mutex wieder freigibt. Auf diese Weise ist die gesamte Zeile vor Unterbrechung geschützt. Um den Logger nun portabel zu halten, sollte das schützende Mutex-Objekt nicht Element des Loggers sein. Leider ist Multithreading meist mit der Verwendung systemspezifischer Bibliotheken verbunden, so daß der Logger dadurch auf ein bestimmtes System festgelegt würde. Um das zu vermeiden, verwendet man ein Proxy-Objekt, welches eine Schnittstelle Lock() und Unlock() anbietet. Dieses nutzerdefinierte Objekt kapselt den eigentlichen, systemspezifischen Mutex. Dieses Objekt (bzw. genaugenommen dessen Typ) wird dem Logger in Form eines Templateparameters übergeben. Dieser ruft die entsprechenden Funktionen dann bei Bedarf auf. Auf diese Weise bleibt der eigentliche Logger-Code unabhängig vom zugrundeliegenden Threadframework. Diese skizzierten Ansätze habe ich im ursprünglichen Logger implementiert, den Code noch etwas aufgeräumt und auf einer eigenen Seite (Vorsicht: Englisch) abgelegt. Dort findet sich auch die Schnittstellendokumentation und etwas übersichtlichere Hinweise zur Benutzung. Samstag, 1. Oktober 2005Bereichsgeprüfte Datentypen in C++Nach all dem privaten, nichttechnischen Kram mal wieder etwas technisches. Schließlich muss man den Unmengen von Lesern (so 3-5) ja auch was bieten. Statt Sex, Drugs and Rock'n'Roll gibt es mal wieder C++ und Templates. Aber von vorn... In einem Anfall von Langeweile habe ich mich mal ein wenig nach Links zur Programmiersprache Ada umgeschaut. Diese wird generell als die Sprache der Wahl zur Entwicklung sicherer Software gehandelt, da sie viele eingebaute Möglichkeiten bietet, Korrektheit im Programm sicherzustellen. Ein nettes Feature, welches mir aufgefallen ist sind bereichsgeprüfte Datentypen. Man kann in Ada einen numerischen Datentyp in folgender Form definieren:
type Time_Of_Day is range 0..86399; Das definiert einen Typ Time_Of_Day, der Werte von 0 bis 86399 annehmen kann. Das hat mich dazu inspiriert, mal wieder ein wenig mit Templates rumzuspielen um sowas in C++ nachzubauen. Ziel war es, möglichst wenig Speicherplatz pro bereichsgeprüfter Variable zu verwenden und möglichst wenig Laufzeitoverhead zu haben. Grundsätzlich kann man einen bereichsgeprüften Datentyp auf zwei Arten implementieren: einmal können die Grenzen als Membervariablen in jedem Objekt abgelegt sein und zum anderen können diese als Templateparameter übergeben werden. Die Entscheidung zwischen beiden Varianten ist eine Abwägung zwischen Codegröße und Datengröße. Die Templatevariante führt bei vielen verschiedenen Typen mit unterschiedlichen Grenzen zu einer erheblichen Vergrößerung des Quellcodes, da für jedes Paar von Grenzen eine Templateinstanz angelegt wird. Auf der anderen Seite führt die Haltung der Grenzen im Objekt selbst bei vielen Objekten zum einem erheblichen Speicherbedarf, da nun jedes Objekt Platz für die Werte der Grenzen braucht. Da es das Ziel war, möglichst wenig Speicher für die Objekte zu verwenden, habe ich mich für die Templatevariante entschieden. Das Template wird wie folgt verwendet: RangeChecked<int, 0, 10> var(5). Hiermit wird eine Variable var mit dem Interval [0,10] angelegt und auf den Wert 5 initialisiert. Da sich 5 doch recht klar in [0,10] befindet, geht das auch gut. Befände sich der Initialisierungswert außerhalb, würde eine Exception vom Typ RangeError geworfen. Das Template stellt verschiedene Operationen für numerische Datentypen (+, -, /, = und so weiter) zur Verfügung. Zu beachten ist hierbei, da immer nur die Zuweisungen geprüft sind. Überschreitet also der Wert eines Ergebnistyps innerhalb einer Gleichung kurzzeitig den Bereich, so passiert nichts, da erst der Zuweisungsoperator prüft. val = 10 + 5 - 7; wäre im obigen Beispiel also kein Problem, da das Endergbenis bei der Zuweisung wieder in [0,10] liegt. Eine Zuweisung Variablen unterschiedlicher Intervalle ist auch möglich, solange der zugewiesene Wert im Intervall des Ziels liegt. Als Templateparameter kommen prinzipiell numerische Typen in Betracht. Jedoch gibt es hier eine Ausnahme: Fließkommazahlen können keine Verwendung finden. Der Grund hierfür liegt in den Regeln des C++-Standards. Dieser spricht folgendes zu non-type parameters: 3 A non-type template-parameter shall have one of the following (optionally cv-qualified) types: Fließkommazahlen als Templateparameter sind also nicht erlaubt (hauptsächlich der erste Teil mit den integral types ist interessant. Eine genaue Begründung dafür kann ich nicht geben. Der Versuch einer Diskussion findet sich in de.comp.lang.iso-c++. Vorherrschende Meinung scheint zu sein, daß sich bei der Instanziierung von Templates mit Fließkommaparametern gewisse Probleme mit der Genauigkeit der Zahlendarstellung ergeben. Das Problem der nicht exakten Darstellbarkeit vieler Fließkommanzahlen in Computern wird bei Vergleichen in Programmen typischerweise dadurch umgangen, daß zwei Werte als gleich definiert werden, wenn sie innerhalb einer Schwelle Epsilon liegen. So vermeidet man Probleme durch Rundungsfehler etc. Im Fall von Templateparametern ist das allerdings nicht so einfach. Dort gibt es nur Gleichheit oder Ungleichheit. So könnte eine etwas groteske Situation entstehen: für folgendes Template existieren die beiden Instanziierungen a und b: template <double d> class Test
{
};
Test<1.0> a;
Test<0.7+0.3> b;
Sind diese beiden Instanzen vom selben Typ? Der gesunde Menschenverstand würde sagen "Ja, denn 0.3+0.7==1.0.". Ein Compiler kann das eventuell anders sehen. Da weder 0.3, noch 0.7 auf gängigen Architekturen exakt binär darstellbar sind, könnte es durch Rundungsfehler dazu kommen, daß statt 1.0 eben nur 0.9999999 entsteht. Verdammt nah dran an der 1.0, aber eben nicht gleich. Eine Verwendung von b an einer Stelle, wo Test<1.0> erwartet wird müßte also scheitern. In dem verlinkten Thread werden einige Ansätz diskutiert, wie dieses Problem zu lösen sei (symbolisch rechnen, nur Literale als Parameter erlauben). Alle diese Ansätze haben gewisse Probleme (Aufwand, Unterschiede zwischen verschiedenen Compilern etc.). Man darf also gespannt sein, ob sich das Standardisierungskommitee dieses Problems annimmt und wenn ja, wie die Lösung aussieht. So, nach all dem Geblubber noch schnell das Template zum Runterladen: RangeChecked.hxx. Der Code ist wieder public domain. Die Verwendung ist im Kopf der Datei nochmal erklärt. Dienstag, 5. Juli 2005Hurra, mein erstes eigenes SF/freshmeat-Projekt!
Endlich hab ich es geschafft, mal ein Tool zu veröffentlichen! Ich bin ja fast von mir selbst begeistert. A-Gen ist ursprünglich aus der Not heraus entstanden, daß alle Webalbumgeneratoren, die ich so gefunden habe entweder zu unflexibel oder zu kompliziert zu benutzen waren oder dynamischen Content voraussetzten (typischerweise PHP oder sowas). Also hab ich angefangen, mir was eigenes zusammenzuhacken, was jetzt irgendwie etwas ausartet und irgendwann mal die Weltherrscha^W^Wden Albummarkt beherrschen soll :)
Freitag, 10. Juni 2005Erweiterung der Template-SpielereienDie Logging-Klasse, die bereits länger existiert, hat nun noch ein paar Verbesserungen erfahren. Zuersteinmal stellte sich beim Einsatz unter Windows ein Fehler heraus. Wenn man nämlich die Klasse in einem Windows-Programm unter Visual Studio verwendet, so kommt es beim Kompilieren im Debug-Modus zu merkwürdigen Fehlermeldungen. Diese beziehen sich bspw. auf die Deklaration der Loglevel. Bei genauerem Hinsehen stellt sich folgendes heraus: Obwohl ich die gesamte Klasse in einen eigenen Namensraum gepackt habe, um Kollisionen mit anderen Paketen zu vermeiden, passiert genau das. Die Namen DEBUG, TRACE und ERROR sind bereits vergeben. Diese werden als Makros vom VS Präprozessor definiert und dienen zur Ausgabe von Debug-Informationen. Da sich der Präprozessor nicht um Namensräume schert, ersetzt er die entsprechenden Token im Quelltext natürlich, was der Compiler richtigerweise mit Syntaxfehlern quittiert. Daher tragen die Level nun alle den Präfig LEVEL_. Das macht leider den Code etwas umfangreicher, vermeidet aber Fehler. Eine weitere Sache, die sich beim Debugging von Code immer recht praktisch macht ist eine Angabe, wo im Quelltext ein Fehler auftaucht. Für diesen Zweck enthält der ANSI-C(und damit auch der C++)-Standard die Makros __FILE__ und __LINE__. Diese werden zur Compilezeit zum aktuellen Dateinamen und der aktuellen Zeilennummer aufgelöst, wo sie auftauchen. Da das recht praktisch ist, findet sich jetzt im Headerfile ein Makro namens HERE, welche zur Compilezeit zu dem String "dateiname:zeile" aufgelöst wird. Um diesen konstanten String zu erhalten muss man noch auf einen kleinen Trick zurückgreifen, da __LINE__ normalerweise als Zahl aufgelöst wird. Ein einfaches Anhängen an den String in __FILE__ ist somit nicht möglich. Zu diesem Zweck definiert man sich folgendes Konstrukt: #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #define HERE __FILE__":"TOSTRING(__LINE__)" " Das Makro STRINGIFY gibt seinen Parameter als String aus. Dieser Parameter kommt wiederum von TOSTRING. Die zweifache Kette von Makros wird notwendig durch die spezifischen Eigenschaften des #-Operators des Präprozessors. Dieser gibt den ihm folgenden Parameter als String aus. Würde man hier nun direkt STRINGIFY(__LINE__) aufrufen, so lautete die Ausgabe "__LINE__", da der Parameter eben so heißt. Durch die Indirektion über TOSTRING passiert folgendes: Im ersten Schritt wird der Aufruf TOSTRING(__LINE__) durch STRINGIFY(zahl) ersetzt. zahl entspricht dabei der aktuellen Zeilennummer. Danach wird dieses Konstrukt rekursiv durch die Stringdarstellung des Parameters von STRINGIFY ersetzt; es entsteht also der Wert "zahl". Diesen kann man nun ganz einfach mit den restlichen Strings verketten lassen. Eine letzte Kleinigkeit ergibt sich aus der Notwendigkeit, Logger auch dynamisch anlegen zu können. Der bisher favorisierte Ansatz, den Logger als globale Variable zu definieren und so vor dem Eintreten in main zu initialisieren, erreicht seine Grenzen, wenn bspw. das Ziel der Loggingausgabe zur Laufzeit erst festgelegt werden soll (bspw. durch einen Kommandozeilenparameter). Dazu muss man in der Lage sein, den Logger innerhalb des normalen Programmflusses dynamisch anzulegen. Hier schafft die Definition einiger zusätzlicher Ausgabeoperatoren Abhilfe. Diese nehmen statt einer Referenz auf einen Logger einen Zeiger auf diesen entgegen, dereferenzieren diesen und rufen darauf dann ihr passendes Gegenstück auf. Dessen Rückgabe (nämlich eine Referenz auf den Logger, auf den der übergebene Pointer zeigt) liefern sie anschließend zurück. Durch diese Operatoren werden Konstruktionen wie die folgende möglich:
Logging::Logger *logger;
int main(int argc, char *argv[])
{
logger = new Logger(std::ofstream(argv[1]);
logger << LEVEL_DEBUG << "A debug message" << Logging::endl;
delete logger;
return 0;
}
Dieses Programm bindet den Logger auf den Dateinamen, den es als erstes Argument bekommt. Theoretisch ist so sogar ein Wechsel des Loggingziels möglich, indem der Logger mittels delete zerstört und danach mit new neu angelegt wird. Den Quellcode kann man natürlich wieder hier runterladen. 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. Montag, 28. März 2005Boost-Library und Pfade
Die Boost-Library ist eine nette Sammlung kleinerer und größerer Tools zur portablen Programmierung in C++. Im Moment hat es mir die Boost Filesystem Library recht angetan, da man damit auf einfache Art und Weise Verzeichnisse portabel behandeln kann. Allerdings hat die bereitgestellte Abstraktion boost::filesystem::path eine kleine Ecke, die manchmal etwas unschön ist: sie checkt Pfade beim Parsen aus der systemspezifischen Form auf syntaktische Korrektheit. Leider stolpert sie dabei über Leerzeichen im Pfad. Ein boost::filesystem::no_check als zweiter Parameter ist hilfreich um diesen Fehler zu vermeiden.
« vorherige Seite
(Seite 2 von 2, insgesamt 25 Einträge)
|