Die Boost C++ Bibliotheken sind eine Sammlung moderner, auf dem C++ Standard basierender Bibliotheken.
Verantwortlich für die Entwicklung und Veröffentlichung der Boost C++ Bibliotheken ist die Boost-Community (www.boost.org).
Es handelt sich hierbei um eine relativ große Gruppe an C++-Entwicklern aus aller Welt. Ziel dieser Community ist es, qualitativ hochwertige Bibliotheken zu entwickeln und zu sammeln, die den C++ Standard ergänzen. Bibliotheken, die sich in der Praxis bewähren und in der Entwicklung von C++-Programmen eine große Bedeutung erlangen, besitzen gute Chancen, eines Tages in den C++ Standard aufgenommen zu werden.
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 annähernd 100 offizielle Bibliotheken, von denen viele voraussichtlich Eingang in den neuen C++-Standard finden werden, z.B.
String-Verarbeitung
Multithreading
asynchrone Ein- und Ausgabe
Zeitangaben
Serialisierung
Interprozesskommunikation,
...
Für den Großteil der Bibliotheken gilt die freie Boost Software License.
Die Installation erfolgt nach der Beschreibung in
#include < iostream >
#include < boost/tokenizer.hpp >
#include < string >
int main() {
std::string str = ";;Hello|world||-foo--bar;yow;baz|";
typedef boost::tokenizer< boost::char_separator< char > > tokenizer;
boost::char_separator< char > sep("-;|");
tokenizer tokens(str, sep);
for (tokenizer::iterator tok_iter = tokens.begin();
tok_iter != tokens.end(); ++tok_iter)
std::cout << "[" << *tok_iter << "] ";
std::cout << "\n";
return EXIT_SUCCESS;
}
Übersetzen und Programmlauf:
$ g++ -I ../../../../boost_1_39_0/ example.cpp -o example $ example [Hello] [world] [foo] [bar] [yow] [baz] $
Hier gibt es einen Überblick über die verfügbaren Bibliotheken. Ein Online-Buch (in deutsch) erklärt einige der boost-Bibliotheken.
Hörsaalübung
C++ hat keine eingebaute Automatik zur Freigabe von auf dem Heap allokierten Speicher. d.h. man muss mittels new konstruierten Objekte explizit durch Aufruf von delete auch wieder freigeben.
Bei einem linearen Programmablauf stellt dies kein Problem dar. In realen Programmen muss man aber jederzeit mit dem Auftreten von Fehlern und Ausnahmen (Exceptions) rechnen. Das ordnungsgemäße Aufräumen des Heaps in sämtlichen Fehlersituationen sicherzustellen, kann ein erheblicher Implementierungs- und Testaufwand bedeuten.
Beispiel (buffer1.cpp):
#include < exception >
#include < iostream >
class Buffer {
public:
Buffer();
~Buffer();
void transcode( int k);
// ...
};
Buffer::Buffer() {
std::cerr << "a new Buffer has been constructed\n";
}
Buffer::~Buffer() {
std::cerr << "the Buffer was destroyed\n";
}
void Buffer::transcode( int k) {
if (k < 0) throw std::exception();
std::cerr << "Buffer transcoded (" << k << ")\n";
}
Die Methode transcode wird u.U. eine Ausnahme!
using namespace std;
int main() {
try {
Buffer* b = new Buffer();
// ...
b -> transcode( -2);
// ...
delete b;
}
catch (const exception& e) {
cerr << "Program exception\n";
exit(1);
}
}
Die Ausgabe des Programms:
$ buffer1 a new Buffer has been constructed
Program exception $
Der Destruktor vom Buffer b wird nie aufgerufen!
Der Grund dafür ist, dass die Funktion transcode eine Ausnahme wirft und somit die Programmausführung direkt im catch Block fortgesetzt wird. Alle im try Block nach transcode stehenden Anweisungen werden nicht mehr ausgeführt, so auch nicht die delete Anweisung. Alos wird auch der von b belegte Speicherplatz nicht freigegeben.
Die STL stellt dazu das Template auto_ptr (automatic pointer) bereit. (buffer2.cpp)
#include < memory >
...
using namespace std;
int main() {
try {
auto_ptr< Buffer > b (new Buffer());
// ...
b -> transcode( -2);
// ...
}
catch (const exception& e) {
cerr << "Program exception\n";
exit(1);
}
}
auto_ptr ist im Header <memory> definiert und bekommt als Templateargument den Typ des zu verwaltenden Objektes. Ein reales auto_ptr Objekt wird dann mittels eines Konstuktors erzeugt, der als Parameter den Zeiger auf den zu verwaltenden Bereich des Heaps bekommt.
Die Ausgabe des Programms:
$ buffer2 a new Buffer has been constructed
the Buffer was destroyed
Program exception $
auto_ptr bietet eine einfache und wirksame Hilfestellung bei der Verwaltung von Heapspeicher und damit der Vermeidung von Speicherlecks.
Zu beachten ist:
Hier schafft boost Abhilfe.
Hörsaalübung
Entwickeln Sie Beispielprogramme, die die o.a. Punkte 3 und 4 demonstrieren.
shared_ptr aus der Boost.smart_ptr-Bibliothek enthält 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.
Beispiel (sharedPtr.cpp)
Zur Demonstration dient die Klasse payload. 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.
class payload {
private:
string id_;
public:
payload(const string& id) {
id_ = id;
}
~payload(){
cout << "Destructor of object" << id_ << endl;
}
void printObjectId(void){
cout << "Called for object" << id_ << endl;
}
};
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.
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;
}
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 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.
Ausgabe:
$ sharedPtr 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 $