Schubkraft

Wer ein neues Projekt beginnt und sich für eine Programmiersprache entscheiden soll, steht einem Dschungel vorgefasster Meinungen gegenüber. Java ist portabel. C# ist eben dieses nicht, aber dafür die Sprache der Zukunft. Genau wie D. Und C++, als Urvater der objektorientierten C-Ableger, gilt als hoffnungslos veraltet. Die Boost-Bibliothek ändert diese Parameter von Grund auf.

vorlesen Druckansicht 66 Kommentare lesen
Lesezeit: 23 Min.
Von
  • Dr. RĂĽdiger Berlich
Inhaltsverzeichnis

Seit seiner Einführung zeichnet sich C++ nicht durch ein Übermaß an vorgefertigten Bibliotheken aus. Gewiss - man kann auf die Funktionen der C-Lib zurückgreifen, und die Einführung der Standard Template Library (STL) hat die C++-Programmierung von Grund auf revolutioniert. Aber im Vergleich zu den Möglichkeiten ihrer Brüder im Geiste, Java und C#, handelt es sich bei den Komponenten der C++-Standardbibliothek eher um kleine Fische. Man denke nur an das Fehlen von Threads und Netzwerkfunktionen als Teil des Standards - von Serialisierung, der automatischen Generierung von Webservices oder dem Datenbankzugriff ganz zu schweigen. Natürlich gab es vielfältige, auch kostenfreie Bibliotheken, die die gröbsten Lücken schlossen. Oft waren diese aber wenig portabel, oder aufgrund ihrer Lizenzen in vielen Bereichen nicht einsetzbar. Einst gefeierter Überflieger, ist C++ deshalb heute eher zur lahmen Ente mutiert.

Mit dem kommenden neuen Standard - in Anlehnung an den Erscheinungstermin „irgendwann vor 2010“ kurzerhand C++0x genannt [1] - erwacht die Sprache nun wieder aus ihrem Winterschlaf. Vorreiter dieser Änderung ist das Boost-Projekt. Unter seinen derzeit 75 offiziellen (sowie weiteren, „kommenden“) Bibliotheken finden sich viele Kandidaten für den neuen Standard. Dieses dreiteilige Tutorial stellt eine Auswahl der Boost-Komponenten vor. Aufgrund der Vielfalt an interessanten Funktionen, die Boost bietet, beschränken sich die Beispiele nicht allein auf den kommenden Standard.

Boost erhielt seinen Namen während einer vermutlich nicht alkoholfreien Diskussion 1998, im Jahr der Verabschiedung des aktuellen C++-Standards. Während eines Dinner-Meetings diskutierten Robert Klarer und Beman Dawes über die Perspektiven für eine neue (verbesserte) C++-Standardbibliothek. Die Boost FAQ berichtet, dass Herb Sutter, der heutige Vorsitzende des C++-Standardkomitees, damals an einem nicht ganz ernst gemeinten Proposal für eine Sprache namens „Booze“ (englisch: Schnaps, Suff) arbeitete. Diese sollte besser sein als Java - der Name steht ja bekanntermaßen für Kaffee. Booze diente als Namenspate für Boost - wie so oft wurde dieser Arbeitstitel beibehalten.

Man könnte hieraus sicherlich einen Rückschluss auf die Stimmung auf Meetings des C++-Standardkomitees ziehen. Für die Entwicklung von Boost wichtiger dürfte aber die Tatsache sein, dass von Anfang an Mitglieder dieser Gruppierung an der Bibliothekssammlung mitarbeiteten und umgekehrt Entwicklungen der Boost-Welt zum neuen Standard beitrugen.

Dabei ist der Entwicklungsprozess der Boost-Bibliotheken durchaus nüchtern: Soll eine neue Bibliothek aufgenommen werden, so durchläuft sie eine rigorose Review-Phase, wobei nicht garantiert ist, dass es ein Kandidat in die Sammlung schafft. Etablierte Bibliotheken durchlaufen für jede neue Release eine Testserie, die unter anderem die Lauffähigkeit auf unterschiedlichen Betriebssystem- und Compiler-Kombinationen sicherstellt. Boost ist auf Portabilität ausgerichtet und profitiert von einem Open-Source-Entwicklungsmodell. Die Erläuterungen zur Boost-Lizenz (siehe Kasten) betonen insbesondere die Anwendbarkeit für kommerzielle Projekte.

Die in diesem Tutorial vorgestellten Beispiele für den Einsatz ausgewählter Boost-Komponenten stehen unter der Boost Software License 1.0 und sind über den iX-Listingsservice erhältlich.

Die Programme des aktuellen Artikels wurden unter Opensuse 10.2 entwickelt, das bereits mit Boost in der Version 1.33.1 ausgeliefert wird. Die gesamte Bibliothek ist unter www.boost.org frei verfĂĽgbar.

Fragt man einen Java-Entwickler, was seine Sprache C++ voraushat, wird er wahrscheinlich auf die Garbage Collection sowie das Fehlen von Pointern hinweisen (die umfangreiche Bibliothekssammlung von Java scheint dabei eher als Selbstverständlichkeit hingenommen zu werden). Ein C++-Programmierer würde darauf vielleicht süffisant erwidern, dass ein Entwickler, der nicht einmal zwischen Speicher-Adressen und -Inhalten unterscheiden kann, wohl besser zurück zum Legobauen geht. Und überhaupt kann es ja wohl nicht so schwer sein, einem new irgendwann ein delete folgen zu lassen.

Daran ist etwas Wahres. Privat und jenseits aller Polemik dürfte aber selbst der eingefleischteste C++-Befürworter zugeben, dass Speichermanagement seine Tücken hat, speziell wenn nicht klar ist, wer letztendlich die Verantwortung für dynamisch allozierten Speicher und dessen Freigabe trägt. Hier stellt die Garbage Collection zwar eine probate, aber nicht besonders performante Lösung dar. Unter C++ erfordert sie zudem einige Umwege.

Bjarne Stroustup empfiehlt in seinem Standardwerk „Die C++ Programmiersprache“, für das Ressourcenmanagement die Verwaltung von Ressourcen an die Initialisierung und Freigabe von Objekten zu koppeln. Automatische Variablen werden beim Verlassen ihres Gültigkeitsbereiches zerstört. Erledigt etwa der Konstruktor mit new die Anlage von Speicher einer Klasse und der Destruktor gibt ihn durch delete wieder frei, so reicht das Anlegen einer automatischen Variable dieses Objekttyps, um die korrekte Freigabe sicherzustellen.

C++ stellt hierfür von Haus aus die Template-Klasse auto_ptr bereit, deren Initialisierung durch einen Zeiger auf einen Speicherbereich erfolgt. Das Operator-Overloading von C++ ermöglicht es, den auto_ptr (fast) wie einen normalen Pointer zu verwenden. Verlässt ein Programm den Gültigkeitsbereich des auto_ptr, wird er zerstört. Sein Destruktor sorgt für den Aufruf von delete auf den in ihm gespeicherten Zeiger. Man bezeichnet den auto_ptr neudeutsch oft als Smart Pointer - er verhält sich wie ein normaler Zeiger, sorgt jedoch am Ende seiner Lebenszeit für die Freigabe des zugehörigen Speicherbereichs.

Leider weist der auto_ptr jedoch erhebliche Schwächen auf. So ist undefiniert, was passiert, wenn zwei auto_ptr dasselbe Objekt referenzieren. Beim Kopieren eines auto_ptr in einen anderen zeigt die Quelle hinterher auf nichts. Dies unterscheidet sich stark von der Situation eines „normalen“ Zeigers. Leider ist es wegen der Kopierproblematik nicht ratsam, einen auto_ptr in einem Standardcontainer zu speichern.

Boost verfügt mit dem shared_ptr aus der Boost.smart_ptr-Bibliothek über eine überraschend einfache Lösung. Im Unterschied zum auto_ptr enthält diese Klasse einen Referenzzähler und ermöglicht es so, dass mehr als ein shared_ptr auf dasselbe Objekt zeigt. Das Kopieren des Inhalts eines shared_ptr in einen anderen ist ebenfalls möglich, ohne dass die Quelle ihre Gültigkeit verliert. Beim Zerstören des letzten shared_ptr wird das referenzierte Objekt als letzte Aktion freigegeben. Dies macht den shared_ptr für eine breite Klasse von Anwendungen nutzbar. Die Verwendung ist denkbar einfach (siehe Listing 1 und Listing 2).

Mehr Infos

Listing 1: Klasse payload

class payload{
public:
payload(const string& id){
id_ = id;
}
~payload(){
cout << "Destructor of object" << id_ << endl;
}
void printObjectId(void){
cout << "Called for object" << id_ << endl;
}
private:
string id_;
};
Mehr Infos

Listing 2: main()

int main(int argc, char **argv)
{
payload *objectA = new payload("A");
payload *objectB = new payload("B");
shared_ptr<payload> A(objectA), B(objectB), C;
cout << "Assigning A to C" << endl;
C=A;
cout << "Calling printObjectId() on A,B.C ..." << endl;
A->printObjectId(); // Dereferenzierung mit -> Operator
(*B).printObjectId(); // Dereferenzierung * Operator
(C.get())->printObjectId(); // Extraktion des Pointers
cout << "Resetting shared_ptr B ..." << endl;
B.reset();
cout << "Resetting shared_ptr A ..." << endl;
A.reset();
cout << "Now C is " << (C.unique()?"unique":"a copy")
<< " while A is " << (A?"not empty":"empty") << endl;
cout << "Leaving the program ..." << endl;
}

Zur Demonstration dient im Folgenden eine Klasse payload, aus der die von den shared_ptr referenzierten Objekte generiert werden. Zu ihrer Unterscheidung erhält der Konstruktor einen Buchstaben als Argument, der sich über die öffentliche Funktion printObjectId() ausgeben lässt. Beim Aufruf des Destruktors meldet auch er sich mit der Objekt-ID (Listing 2).

Zunächst legt das Beispiel in main mit new zwei Pointer auf payload-Objekte an, denen es die IDs A und B zuweist. Danach legt es drei shared_ptr-Objekte an - A und B zeigen jeweils auf die zugehörigen payload-Objekte, und C ist eine Kopie von A.

Der Aufruf der printObjectId()-Funktion für die payload-Objekte kann auf drei Arten erfolgen: Wie bei einem normalen Pointer lassen sich shared_ptr-Objekte über den Pfeil- und den Stern-Operator dereferenzieren. Man erhält so Zugang zu den Methoden von payload. Alternativ kann man mit der get()-Funktion von shared_ptr direkt auf den gespeicherten payload-Zeiger zugreifen.

Zunächst setzt man nun mit shared_ptr<T>::reset() den shared:ptr B zurück. Da es den einzigen Pointer auf objectB enthält, ruft dies automatisch dessen Destruktor auf. Von objectA gibt es jedoch eine Kopie im shared_ptr C. Setzt man A entsprechend zurück, bleibt objectA intakt. Demgemäß darf an dieser Stelle keine Ausgabe vom Destruktor erfolgen. Mit unique() lässt sich überprüfen, ob C die letzte Kopie besitzt.

Interessant ist die automatische Konvertierung des shared_ptr in einen bool-Wert - false steht für einen shared_ptr, der keinen gültigen Pointer mehr enthält. Entsprechend kann man vor einer Operation testen, ob ein shared_ptr einen gültigen Inhalt besitzt. Die Sequenz (A?"not empty":"empty") liefert nach dem Reset von A "empty" zurück. Beim Verlassen des Programms verlieren alle shared_ptr ihren Gültigkeitsbereich. Das ruft nun endlich den Destruktur von objectA auf, auf das C zeigt. Die Ausgabe des Beispielprogramms bestätigt diese Ausführungen:

Assigning A to C
Calling printObjectId() on A,B,C ...
Called for objectA
Called for objectB
Called for objectA
Resetting shared_ptr B ...
Destructor of objectB
Resetting shared_ptr A ...
Now C is unique while A is empty
Leaving the program ...
Destructor of objectA

Beim Übersetzen des Programms muss man sich übrigens keine Gedanken darüber machen, dem Compiler Bibliotheken für den Linker mitzuliefern. Viele der Boost-Bibliotheken sind „Header-only“. Im Falle des shared_ptr reicht es, die Header-Datei <boost/shared_ptr.hpp> einzubinden.

Mit den wenigen in den Beispielen gezeigten Befehlen lassen sich die meisten Anwendungsfelder abdecken. Zu beachten ist dabei, dass der Programmierer an keiner Stelle selbst ein delete einsetzen muss. Das Löschen der payload-Objekte erfolgte genau zu dem Zeitpunkt, zu dem sie nicht mehr benötigt wurden. Man kann hiermit nun ähnlich sorglos agieren wie mit Javas new samt Garbage Collector. Speicherlecks in C++ sollten damit der Vergangenheit angehören.

shared_ptr sind normale Objekte und eignen sich beispielsweise als Rückgabewert einer Funktion. So könnte man sich eine Factory-Funktion vorstellen, die bei jedem Aufruf einen mit einem payload-Objekt initialisierten shared_ptr zurückliefert. Unter normalen Umständen wäre es schwierig, dafür zu sorgen, dass das Programm die payload-Objekte wieder löscht, insbesondere wenn es die Pointer weiterreicht. Mit shared_ptr ist dies kein Problem mehr. Verlässt die letzte Kopie ihren Gültigkeitsbereich, wird gleichzeitig das payload-Objekt gelöscht. Analog ist es möglich, Pointer in STL-Containern zu speichern - man muss sie lediglich in einem shared_ptr kapseln. Dieses Vorgehen zeigt Listing 3.

Mehr Infos

Listing 3: shared_ptr als RĂĽckgabewert

shared_ptr<payload> payloadFactory(const string& id){
payload *retVal = new payload(id);
shared_ptr<payload> retValPtr(retVal);
return retValPtr;
}
int main(int argc, char **argv){
vector<shared_ptr<payload> > payloadContainer;
for(unsigned int i=0; i<3; ++i){
string id = lexical_cast<string>(i);
payloadContainer.push_back(payloadFactory(id));
}
}

Eine Funktion payloadFactory erzeugt dort shared_ptr<payload>-Objekte samt id. Mit ihr füllt das Programm in einer Schleife einen Vektor der Standard Template Library. Hier stellt sich die Frage, wie man die Laufvariable in den von der Factory erwarteten Typ string wandelt. Üblicherweise ist dies recht mühsam - an dieser Stelle ergibt sich jedoch die Gelegenheit, eine weitere Neuerung von Boost einzuführen: lexical_cast ermöglicht die intuitive Umwandlung zwischen Zahlen und verschiedenen String-Formaten. Listing 3 wandelt einen unsigned int in einen string, den die payloadFactory erwartet.

Schlägt die Umwandlung fehl, wirft das Programm eine Ausnahme bad_lexical_cast. Auf deren Auffangen verzichtet das Beispiel, da es lediglich eine Laufvariable übergibt. Der lexical_cast kann aber noch viel mehr. Versieht man seine Klassen mit einem passenden operator << und operator >, kann lexical_cast auch mit diesen umgehen.

Am Ende von main() erfolgt das Löschen des Vektors payloadContainer zusammen mit den darin enthaltenen Objekten. Wie erwartet melden sich an dieser Stelle die drei Destruktoren der payload-Objekte, sobald der Destruktor des Vektors ihn leert (was hier nicht mehr gezeigt werden soll). Wichtig ist festzuhalten, dass der shared_ptr endlich auch die Vektoren der STL für die Verwendung mit Zeigern fit macht.

Bleibt noch die Frage, wie sich der shared_ptr zusammen mit Threads verhält. Die Dokumentation besagt, dass shared_ptr-Objekte denselben Grad an Thread-Sicherheit bieten wie eingebaute Typen. Insbesondere können sie aus mehreren Threads heraus gleichzeitig gelesen werden. Unterschiedliche Threads können gefahrlos Kopien desselben shared_ptr besitzen und beispielsweise zurücksetzen. Dies ist nicht selbstverständlich, da sich solche Kopien ja einen Referenzzähler teilen. Für Zugriffe auf den im shared_ptr gespeicherten Zeiger gelten dieselben Regeln wie für Zugriffe ohne shared_ptr. Gegebenenfalls muss der Programmierer einen solchen Zugriff schützen, etwa durch einen Mutex.

Zum Schluss dieses Abschnitts noch ein Wort der Warnung: Der shared_ptr kann seine Magie nur entfalten, wenn er - und nur er - allein für seine Zeiger verantwortlich ist. Verwendet man parallel die „rohen“ Zeiger anderweitig, führt dies zu Chaos. Der shared_ptr glaubt sich ja im Besitz dieses Zeigers und löscht das referenzierte Objekt unter Umständen.

C++ bietet die Möglichkeit, die meisten Operatoren umzudefinieren. Beim shared_ptr haben die Beispiele dies schon mit den Operatoren * und -> gezeigt. Gelegentlich will ein Programmierer seinen Klassen eigene arithmetische Operatoren mitgeben. Möchte er Klassen schaffen, mit denen ein Anwender wirklich rechnen kann, erwartet dieser zu Recht das Vorhandensein des gesamten Satzes an Operatoren für diese Klasse und nicht nur der am häufigsten gebrauchten. Hierzu zählen neben reinen Rechen-, die Vergleichsoperatoren. Die Implementierung all dieser Funktionen ist jedoch nicht nur umfangreich, sondern zudem langweilig und wird deshalb oft übergangen.

Glücklicherweise lassen sich viele Operatoren aus anderen ableiten. So ist etwa die Aussage x>=y äquivalent zu !(x<y). Boosts operators-Bibliothek (Header <boost/operators.hpp>) nimmt dem Entwickler auf diesem Wege viel Arbeit ab. Er muss nur noch die Operatoren implementieren, aus denen sich die anderen ableiten lassen. Dann verfügt er automatisch über einen kompletten Satz. Eine rudimentäre Integer-Klasse könnte wie in Listing 4 deklariert sein.

Mehr Infos

Listing 4: Die Klasse myInt

class myInt
:boost::operators<myInt>
{
public:
myInt(void);
myInt(int):
myInt(const myInt&);
int value(void) const;
myInt& operator=(const myInt&);
bool operator<(const myInt&) const;
bool operator==(const myInt&) const;
myInt& operator+=(const myInt&);
myInt& operator-=(const myInt&);
myInt& operator*=(const myInt&);
myInt& operator/=(const myInt&);
myInt& operator%=(const myInt&);
myInt& operator|=(const myInt&);
myInt& operator&=(const myInt&);
myInt& operator^=(const myInt&);
myInt& operator++();
myInt& operator--();
private:
int myVal_;
};

Die Implementierung dieser Funktionen ist nicht weiter schwierig. Der Operator < könnte etwa einfach so aussehen:

bool myInt::operator<(const myInt& x)
const{
return myVal_ < x.myVal_;
}

Durch die private Ableitung von boost::operators<myInt> stehen nun auf triviale Weise auch Operatoren wie <=, != oder > zur Verfügung, obwohl man sie nie selbst definiert hat. Der binäre Operator + existiert ebenfalls. Eine kurze main()-Funktion soll das illustrieren (Listing 5).

Mehr Infos

Listing 5: Operatoren in main()

int main(int argc, char** argv){
myInt a(1), b(2), c=a+b;
cout << "a = " << a.value() << endl
<< "b = " << b.value() << endl
<< "c = " << c.value() << endl;
if(a<=b) cout << "a is smaller or equal b" << endl;
if(a!=b) cout << "a is not equal to b" << endl;
if(c>b) cout << "c is larger than b" << endl;
}

Zunächst erfolgt die Initialisierung zweier myInt-Objekte a und b, ein drittes myInt c entsteht als Summe der beiden ersten. Anschließend vergleicht das Programm die myInt-Objekte miteinander. Die Ausgabe ist wie erwartet und aus der main()-Funktion ablesbar.

C++ legt in Klassen implizit eine Reihe von Funktionen an, wenn der Programmierer sie nicht selbst definiert. Dies stellt beispielsweise die Kopierbarkeit von Objekten sicher, kann aber auch eine Quelle schwer zu entdeckender Fehler sein. Ohne ein konkretes Beispiel zu geben, sei hier darauf hingewiesen, dass Boost als Lösung eine kleine Hilfsklasse noncopyable anbietet. Leitet man von ihr privat eine Klasse ab, ist ein Kopieren des Objekts etwa über den Operator = nicht mehr möglich. Der Compiler entdeckt den Fehler schon zur Übersetzungszeit (Headerfile <boost/utility.hpp>).

Wo STL-Algorithmen Funktionen als Parameter erwarten, setzen Programmierer neben „echten Funktionen“ oft Funktionsobjekte ein. Dabei handelt es sich um Klassen, die den Funktionsaufruf-Operator operator() definieren. Dies eröffnet unter anderem die Möglichkeit, zwischen zwei Aufrufen des Funktionsobjekts Zwischenergebnisse zu speichern. Die STL liefert bereits eine Reihe vordefinierter Funktionsobjekte mit, etwa less<T>() und greater<T>(), die zum Sortieren eines Containers dienen können. Mit den STL-Adaptern bind1st(op,wert) und bind2nd(op,wert) lassen sich vorgegebene Werte an den ersten oder zweiten Parameter eines Funktionsobjekts binden, in der Art op(wert, parameter) oder op(parameter, wert). Zweistellige Operationen werden so zu einstelligen. So liefert std::bind2nd(less<int>(),5)(x) den Wert true zurück, falls x kleiner als 5 ist.

Mit boost::bind (Header <boost/bind.hpp>) steht eine Generalisierung dieses Konzepts zur Verfügung. Sie unterstützt beliebige Funktionsobjekte, Funktionszeiger und sogar Zeiger auf Memberfunktionen von Klassen. Bemerkenswert ist, dass man nicht einmal einen Unterschied zwischen herkömmlichen Zeigern und dem shared_ptr aus Boost machen muss.

Argumente in beliebigen Positionen sind mit beliebigen Werten kombinierbar, und die Zahl der Argumente ist nicht auf 2 beschränkt. Ein zu obigem äquivalenter Ausdruck wäre in Boost bind(std::less<int>(), _1, 5)(x). _1 steht als Platzhalter für x. x und 5 werden an less<int> gebunden, dienen also als dessen Argumente.

Das nächste Beispiel geht einen Schritt zurück zur Speicherung der shared_ptr in STL-Containern. Ziel ist es, die shared_ptr anhand der IDs der referenzierten payload-Objekte zu sortieren. Als Sortierkriterium taugen die shared_ptr natürlich nicht, dazu muss man auf die ID der Objekte zugreifen. Hierzu wird zunächst eine neue Memberfunktion getObjectId() in die payload-Klasse aus Listing 1 eingefügt:

string payload::getObjectId(void){
return id_;
}

Mit dem vordefinierten Funktionsobjekt std::greater<string> der STL steht bereits die richtige Methode bereit, um die payload-Objekte zu sortieren. Hieraus muss das Beispielprogramm nur noch ein passendes Funktionsobjekt generieren, das die speziellen Verhältnisse des vector<shared_ptr<payload> > berücksichtigt. Listing 6 zeigt die passende main()-Funktion.

Mehr Infos

Listing 6: Funktionsobjekt mit boost::bind

int main(int argc, char **argv){
vector<shared_ptr<payload> > payloadContainer;
// payloadFactory erzeugt einen shared_ptr<payload>
payloadContainer.push_back(payloadFactory("A"));
payloadContainer.push_back(payloadFactory("B"));
payloadContainer.push_back(payloadFactory("C"));
sort(payloadContainer.begin(),
payloadContainer.end(),
bind(
std::greater<string>(),
bind(&payload::getObjectId, _1),
bind(&payload::getObjectId, _2)
)
);
for_each(payloadContainer.begin(),
payloadContainer.end(),
bind(&payload::printObjectId,_1));
}

Zunächst füllt das Beispiel einen vector<shared_ptr<payload> > mit passenden Objekten und vergibt die IDs A, B und C. Es folgen die Sortierung des Vektors sowie letztendlich für jede seiner Positionen ein Aufruf der printObjectId()-Funktion der payload-Klasse. Hierbei darf man nicht vergessen, dass im Vektor nicht die payload-Objekte selbst, sondern nur die shared_ptr Objekte gespeichert sind, die auf die eigentlichen Objekte zeigen.

Mit dieser Information sollte der Ausgabeteil der main()-Funktion verständlich werden - hier wird einfach ein shared_ptr<payload> (angezeigt durch _1) als erstes Argument an bind() übergeben. bind ist in der Lage, mit einem shared_ptr umzugehen und ruft, wie gewünscht, die printObjectId-Funktion auf.

Auf dieser Grundlage sollte das Sortieren des Vektors verständlich sein. Hier werden einfach verschiedene bind-Aufrufe geschachtelt. sort() wendet das durch bind bereitgestellte Funktionsobjekt jeweils auf zwei der im Vektor gespeicherten shared_ptr-Objekte an und vergleicht sie mit seiner Hilfe. Für beide ruft bind() die getObjectId()-Funktion auf. Der Rückgabewert ist jeweils die ID als std::string. Diese dienen wiederum dem äußeren bind-Aufruf als Argumente, die an den std::greater<string>-Aufruf gebunden sind. So lassen sich auf einfache Weise passende Funktionsobjekte für die Sortierung generieren. Die Ausgabe der main()-Funktion (unter Vernachlässigung der Destruktorausgaben) lautet nun einfach

Called for objectC
Called for objectB
Called for objectA

Wie gewünscht hat sich die Reihenfolge des Vektors geändert. Würde man bei der Sortierung _1 und _2 vertauschen, fände übrigens die Sortierung in umgekehrter Reihenfolge statt, äquivalent zum Ersetzen von greater<> durch less<> in Listing 6.

Wer den Erläuterungen bis hierhin gefolgt ist, mag vielleicht Lust auf mehr Informationen bekommen haben (oder ist so erschlagen, dass er/sie weitere Quellen benötigt). Die Qualität der Dokumentation ist leider für die verschiedenen Bibliotheksteile sehr unterschiedlich. Im Falle etwa von boost::bind und shared_ptr sollte man aber auf der Boost-Webseite fündig werden. Auch Björn Karlssons Buch „Beyond the C++ Standard Library“ [2] ist ein guter Einstieg. Nicht wundern sollte man sich darüber, dass praktisch alle verfügbaren Informationen auf Englisch sind.

Im Zusammenhang mit boost::bind hätte man durchaus noch auf die boost::function-Bibliothek eingehen können. Sie erlaubt die Speicherung von Funktionszeigern und -Objekten zwecks späteren Aufrufs. Der zweite Teil des Tutorials wird das nachholen und außerdem die Thread-Programmierung unter Boost beleuchten. Darüber hinaus wird die date_time-Bibliothek zum Umgang mit Zeit-Ausdrücken Gegenstand des nächsten Teils sein.

Dr. RĂĽdiger Berlich
fĂĽhrt am Institut fĂĽr Wissenschaftliches Rechnen des Karlsruhe Institute of Technology (KIT) eine AusgrĂĽndung aus dem Bereich der Parameteroptimierung in verteilten Umgebungen durch.

[1] Bernhard Merkle; Im Aufschwung; C++0x: Ausblick auf den geplanten C++-Standard; iX 6/07, S. 60

[2] Björn Karlsson; Beyond the C++ Standard Library; Addison-Wesley, 2006, ISBN 0-321-13354-4

Mehr Infos

iX-TRACT

  • Die Boost-Bibliothek ergänzt die C++-Standardbibliothek um häufig benötigte Funktionen, beispielsweise Shared Pointer oder die UnterstĂĽtzung fĂĽr die einfache Implementierung von Operatoren.
  • Zurzeit umfasst das Boost-Projekt 75 offizielle Bibliotheken, von denen viele voraussichtlich Eingang in den neuen C++-Standard finden werden.
  • FĂĽr den GroĂźteil der Bibliotheken gilt die freie Boost Software License, die allerdings Fragen darĂĽber offen lässt, wann ein im Sourcecode vertriebenes Programm als von Boost-Komponenten abgeleitetes Werk gilt und damit besondere Bedingungen erfĂĽllen muss.
Mehr Infos