C++ Bibliotheken zur Lösung von Spezialanforderungen

Boost und Qt – C++ Tools für alle Fälle
Kommentare

Wer bei der Erstellung von C++-Anwendungen über das Niveau von einführenden Beispielen hinausgeht, wird schnell an einen Punkt kommen, an dem die Bestandteile aus dem offiziellen C++-Standard nicht mehr ausreichen. Auf der Suche nach komplett ausprogrammierten Algorithmen oder Lösungen für Spezialanforderungen begibt man sich dann in die Welt der externen Bibliotheken, die Sammlungen solcher in vielen Projekten bereits erfolgreich eingesetzter Tools sind. Mit Boost und Qt werden zwei der bekanntesten C++-Bibliotheken in diesem Artikel vorgestellt.

„Selber machen oder einkaufen“? Vor dieser Frage steht man bei der Softwareerstellung häufig. Viele Anforderungen bestanden in der Vergangenheit bereits in anderen Projekten und wurden dort schon gelöst. Warum sollte man also nicht auf diesen Erfahrungsschatz zurückgreifen, anstatt alles zeit- und kostenaufwändig selbst zu implementieren? Wie bei anderen Programmiersprachen auch, existieren im C++-Bereich zahlreiche Frameworks, die – in vielen Fällen kostenlos – eine solche Sammlung von kleinen oder großen Fragmenten beinhalten, mit der die Erstellung komplexer C++-Anwendungen deutlich beschleunigt und aufgrund der bereits bewiesenen Praxistauglichkeit dieser Toolsammlungen auch fachlich verbessert werden kann.
Aus der Vielzahl der verfügbaren externen C++-Bibliotheken wurden für diesen Artikel Boost und Qt ausgewählt. Während Boost eine Art „Bibliothek für alle Fälle“ ist, spielt Qt seine Stärken insbesondere bei der Erstellung von GUI-Anwendungen aus. Der GUI-Bereich ist auch der Bereich, in dem man möglicherweise als Erstes nach externen Bibliotheken bei der C++-Entwicklung sucht, da der offizielle C++-Standard hierfür nichts im Angebot hat. Vor allem für C++-Neueinsteiger, die zuvor im Java-Bereich gearbeitet haben, ist dies sehr ungewohnt, da GUI-Komponenten in der Standard-Java-Welt selbstverständlich dazugehören.
Neben Qt als bekanntester C++-GUI-Bibliothek existieren aber auch noch weitere Alternativen, die an dieser Stelle nur kurz erwähnt werden sollen. Zu nennen sind dabei u. a. wxWidgets, GTK+ sowie für die Zielplattform Windows die Microsoft Foundation Classes. Aber Qt ist nicht nur im GUI-Bereich einsetzbar. Es enthält beispielsweise auch eine eigene Threading-Lösung und kann somit als Alternative zu den im zweiten Teil dieser Artikelserie vorgestellten externen C++-Parallelprogrammierungsbibliotheken eingesetzt werden. Auch für andere Einsatzgebiete, z. B. SQL-Datenbankanbindung oder Netzwerkprogrammierung enthält Qt Hilfsmittel.
Boost dagegen ist eine sehr umfangreiche Bibliothek für verschiedenste Einsatzzwecke. Wer mitten in einem C++-Implementierungsprojekt vor der Frage steht, ob für eine in seinem Projekt benötigte Funktionalität eine externe Bibliothek zur Verfügung steht und eine entsprechende Google-Suche startet, der wird in den meisten Fällen ein Angebot aus den Boost-Bibliotheken in seiner Trefferliste vorfinden.

Boost-Überblick

Boost kann unter einer Boost-eigenen Lizenz frei eingesetzt werden. Hinter Boost steht nicht eine einzelne Firma, sondern gemäß den bekannten Open-Source-Grundsätzen eine Community von weltweiten C++-Entwicklern, die zum Teil auch an der Entwicklung des offiziellen C++-Standards beteiligt sind. Die Boost-Community ist sehr aktiv, kommuniziert über die zentrale Boost-Webseite und trifft sich regelmäßig auf Konferenzen wie der jährlichen BoostCon in Aspen, die seit der Veranstaltung 2012 nun „C++Now!“ heißt und sich damit auch inhaltlich noch mehr einer Eigenschaft von Boost widmet, die in den letzten Jahren immer schon zu beobachten war, nämlich der Tatsache, dass Boost häufig als eine Art Brutkasten für neue C++-Features bezeichnet wird. Während der Weg bis zur Verabschiedung eines neuen C++-Standards sehr lang sein kann, sind neue Tools und Sprachbestandteile häufig zuerst in Boost verfügbar. Ein Beispiel dafür ist die Thread-Implementierung von Boost, die nach langen Diskussionen nun endlich als Grundlage für die Thread-Lösung des C++11-Standards übernommen wurde. Fragt man C++-Entwickler, wonach sie in Vorstellungsgesprächen inhaltlich am häufigsten gefragt werden, so lautet die Antwort meist „Boost“: Wer sich in vorangegangenen Projekten bereits mit der Boost-Bibliothek beschäftigt hat und somit Kenntnisse über die neuesten C++-Erweiterungen nachweisen kann, sammelt in jedem Fall Pluspunkte für die Jobsuche.

Im Schnitt viermal pro Jahr wird ein neues Release der Boost-Bibliothek veröffentlicht, in dem sowohl innerhalb der Boost-Community komplett neu entwickelte Komponenten als auch externe Projekte enthalten sind, die von der Community in das Boost-Projekt aufgenommen wurden. Seit den Anfängen des Boost-Projekts im Jahr 1998 ist so eine hohe Anzahl an Releases veröffentlicht worden. Zum Zeitpunkt der Erstellung dieses Artikels im Herbst 2012 liegt Version 1.51 vor.
Boost ist streng genommen nicht eine einzige Bibliothek, sondern eine Bibliothekssammlung. Das erkennt man einerseits am Umfang der Boost-Funktionalität, andererseits aber auch an den sehr unterschiedlichen Zielgruppen der einzelnen Bestandteile. Die Verwendung von Boost-Teilen erfolgt allgemein (im folgenden Beispiel anhand einer Boost-Koponente xyz) über folgendes Codefragment:

#include <boost/xyz.hpp> 

// ... 
using namespace boost::xyz

Neben dem Inkludieren des Headers benötigt man dann beim Linken noch die zugehörige Library, die über die Option -lboost_xyz eingebunden werden muss. Häufig verwendete Bestandteile aus Boost, die wohl fast jeder C++-Entwickler, der schon viele Jahre dabei ist, bereits in Projekten eingesetzt hat, sind u. a. die String-Verarbeitung (z. B. der unten noch an einem Beispiel vorgestellte Tokenizer) sowie die Multithreading-Bibliothek. Listing 1 zeigt ein Beispiel für den Einsatz von Boost-Threads auf der Basis der in Entwickler Magazin 6.2012 beschriebenen Konzepte für (C++-)Parallelprogrammierung.
Im Folgenden sollen nun einige Boost-Bestandteile kurz vorgestellt werden. Die Auswahl ist natürlich subjektiv. Jedem C++-Entwickler kann nur empfohlen werden, sich selber auf den Überblicksseiten von Boost umzuschauen bzw. bei konkreten Projektanforderungen einen Blick auf die dort veröffentlichte Liste der einzelnen Boost-Projekte zu werfen.

#include <boost/thread.hpp>

boost::mutex mutex; 
int zaehler;

void do_it() { 
    
    boost::lock_guard<boost::mutex> lock(mutex); 
    zaehler++;
    // ... 
}


main() {
    boost::thread thread_1(do_it); 
    boost::thread thread_2(do_it); 

    thread_1.join();
    thread_2.join();
}

Boost-Stringverarbeitung

Mit der Klasse std::string steht bereits im C++-Standard eine Vielzahl von Methoden für den String-Einsatz zur Verfügung. Darüber hinaus hat aber auch Boost in einigen Projekten weitere Angebote für komplexere String-Operationen im Angebot. Neben der später noch an einem eigenen Beispiel beschriebenen Verwendung von regulären Ausdrücken sind dabei insbesondere die Boost-Bibliotheken StringAlgorithms und Tokenizer zu nennen. Die Bibliothek StringAlgorithms ist eine Sammlung von Funktionen, mit denen u. a. Strings vom Typ std:string aus dem C++-Standard verarbeitet werden können. Beispiele sind

to_upper und to_lower zur Umwandlung in Groß- bzw. Kleinbuchstaben
find_first, find_last, find_nth zur Suche von Teilstrings an bestimmten Stellen
• join zum Zusammensetzen mehrerer Teil-Strings
• Inhaltliche Tests von Strings, z. B. is_digit() und is_upper() zum Test, ob der String nur Ziffern bzw. nur Großbuchstaben enthält.

Besteht die Aufgabe nicht nur aus der Suche nach bestimmten Teil-Strings, sondern aus einer Verarbeitung aller Bestandteile eines Strings, so kommt ein Tokenizer zum Einsatz. Über einen Tokenizer kann man über Teilausdrücke eines Strings iterieren. Im folgenden Beispiel werden alle Teile eines Strings einzeln mit einem Zeilenumbruch nach jedem Teilausdruck ausgegeben:

std::string input_string = "Dies ist ein Test für Boost";
boost::tokenizer<> my_tokenizer(input_string);

for(tokenizer<>::iterator next_token=my_tokenizer.begin(); 
    next_token!=my_tokenizer.end();
    ++next_token){
      std::cout << *next_token << "n";
}

Welcher Separator für die Trennung der einzelnen Teilausdrücke verwendet werden soll, kann als optionaler Parameter beim Anlegen des Tokenizers in der Zeile

boost::tokenizer<> my_tokenizer(input_string);

angegeben werden. In obigem Beispiel wurde kein Separator angegeben, sodass gemäß der Default-Einstellung „Leer- und Interpunktionszeichen“ als Trennzeichen verwendet werden.

Aufmacherbild: Tools in open toolbox on plain background von Shutterstock / Urheberrecht: STILLFX

[ header = Seite 2: Smart Pointer ]

Smart Pointer

Eine der häufigsten Fehlerquellen in C++-Anwendungen sind fehlende Speicherfreigaben. Da man bei der C++-Programmierung wesentlich mehr Freiheiten, damit aber auch mehr Verantwortung für den Umgang mit direkten Speicherzugriffen hat, kommt es immer wieder vor, dass ein mittels new reservierter Speicherbereich nicht mehr über eine spätere delete-Anweisung freigegeben wird. Dies kann einerseits durch explizites Auslassen der delete-Anweisung passieren. Was aber mindestens genauso häufig vorkommt und in der Regel bei der Fehlersuche schwerer zu finden ist, ist eine Situation, bei der eine delete-Anweisung zwar vorhanden ist, aufgrund einer zuvor eingetretenen Fehlersituation aber nicht erreicht wird, da durch den Fehler eine Exception ausgelöst wird, die zum Verlassen des aktuellen Programmpfads führt. Folge solcher Speicherfehler können ein stetig wachsender Speicherbedarf eines lang laufenden Programms sein (Memory Leak) oder eine Blockade, wenn zu einem späteren Zeitpunkt erneut auf eine Ressource zugegriffen werden soll, die aber aus dem beschriebenen Grund nach dem letzten Zugriff nicht freigegeben wurde.
Eine Lösung für dieses Problem ist der Einsatz von Smart Pointern, die das von ihnen referenzierte Objekt automatisch freigeben, sobald es nicht mehr benötigt wird, also beispielsweise sobald sein Gültigkeitsbereich verlassen wird. Die Boost-Bibliotheken bieten mehrere Implementierungen von Smart Pointern an, u. a. den boost::shared_ptr. Das folgende Beispiel zeigt den Einsatz eines solchen Smart Pointers:

boost::shared_ptr<Beispielklasse> sp_beispielklasse(new Beispielklasse);

Hier wird nicht direkt mit einem Objekt der angegebenen Beispielklasse gearbeitet, für das zunächst mit new Speicherplatz angelegt und anschließend mit einem delete wieder freigegeben werden muss. Stattdessen wird dieses Objekt direkt in einen Smart Pointer verpackt und mit diesem dann weitergearbeitet. Wird nun der Gültigkeitsbereich dieses Smart Pointers verlassen, so gewährleistet der dabei automatisch aufgerufene Destruktor der Smart-Pointer-Klasse die Freigabe des bei der Initialisierung des Smart Pointers angegebenen Nutzerobjekts. Das diesem Vorgehen zugrunde liegende Konzept heißt RAII (Resource Acquisition is Initialization) und wird u. a. auch in der Multithreading-Lösung in Listing 1 durch den Einsatz von boost::lock_guard verwendet. Im Gegensatz zur klassischen Mutex-Verarbeitung der folgenden Art

mutex.lock();
...
mutex.unlock();

bei der das Problem auftreten kann, dass bei einer Exception vor dem unlock()-Aufruf der Mutex nicht freigegeben wird und somit andere Threads blockiert werden können, garantiert boost::lock_guard durch einen automatischen Destruktor-Aufruf diese Freigabe unabhängig davon, ob der folgende Codeblock regulär am Ende oder außerplanmäßig vorzeitig aufgrund eines Fehlers verlassen wird.

Reguläre Ausdrücke mit Boost

Im Rahmen von komplexen Textverarbeitungsalgorithmen, aber auch bei vergleichsweise kleinen Anforderungen wie der Überprüfung von Benutzereingaben, werden häufig reguläre Ausdrücke eingesetzt. Mit ihnen gibt man ein bestimmtes Format beispielsweise für eine Textfeldeingabe vor, das für die weitere Verarbeitung dieser Eingaben erwartet bzw. benötigt wird. Wer in der Unix-/Linux-Welt arbeitet, wird solche Ausdrücke zum Beispiel aus dem Einsatz von awk, grep oder sed kennen, bei denen man die in Texten zu suchenden bzw. zu ersetzenden Ausdrücke als Parameter in diesem Format angeben muss.
Für den Einsatz in C++-Anwendungen ist die regex-Bibliothek aus dem Boost-Projekt eine Möglichkeit zur Nachbildung dieser Funktionen. Im folgenden Beispiel wird zunächst ein regulärer Ausdruck regex_beispiel angelegt, der anschließend innerhalb eines Input-Strings nach dem angegebenen Muster (ein oder zwei Großbuchstaben, ein Leerzeichen und zwei Ziffern) sucht:

bool check_string(const string input_string)
{
    boost::regex regex_beispiel("\u{1,2} \d{2}");
    return regex_match(input_string, regex_beispiel);
}

Weitere Funktionen im regex-Paket sind beispielsweise regex_replace zum Ersetzen einzelner String-Bestandteile und regex_split zum Aufteilen eines Strings an vordefinierten Stellen.

Qt-Überblick

Wer die Frage nach einem GUI-Framework für C++-Anwendungen stellt, wird in der Antwort eigentlich immer auch den Begriff Qt erhalten. Auch wenn man in Diskussionforen manchmal den Eindruck gewinnen kann, dass Qt genauso viele Anhänger wie Kritiker hat, muss man unabhängig von persönlichen Präferenzen die hohen Marktanteile von Qt neidlos anerkennen. Eines der bekanntesten Systeme, das auf der Basis von Qt entwickelt wurde, ist die Linux-Benutzungsoberfläche KDE.
Bezüglich seiner „Eigentumsverhältnisse“ hat das Qt-Framework in den letzten Jahren bewegende Zeiten erlebt. Lange Jahre wurde es von der norwegischen Firma Trolltech weiterentwickelt, die dann 2008 von Nokia aufgekauft wurde. Nach der Entscheidung Nokias für eine enge Zusammenarbeit mit Microsoft im mobilen Bereich wurden die Qt-Aktivitäten Nokias allerdings immer weiter zurückgefahren. In diesem Zusammenhang gab es Ende 2011 einen wichtigen Schritt für die Zukunft von Qt, als sich Nokia entschied, die Weiterentwicklung von Qt im Rahmen eines Open-Source-Projekts durchführen zu lassen. Im Sommer 2012 schließlich wurden sämtliche Qt-Aktivitäten von Nokia an das finnische Softwareunternehmen Digia übergeben, das in einer ersten Ankündigung eine zügige Verfügbarkeit von Qt auch für die Android-, iOS- und Windows-8-Welt in Aussicht stellte. Lizenztechnisch orientiert sich Qt an einem aus anderen Open-Source-Produkten bekannten dualen Modell. Seit der Version 4.5, die im März 2009 veröffentlicht wurde und unter der LGPL 2.1-Lizenz erhältlich ist, ist auch ein kommerzieller Einsatz kostenfrei möglich. Auf Wunsch sind aber natürlich auch kostenpflichtige Lizenzpakete erhältlich. Die zentrale Komponente des Qt-Frameworks ist die eigene Entwicklungsumgebung Qt Creator (Abb. 1).

Abb. 1: Qt Creator

Insbesondere für Neueinsteiger enthält sie u. a. eine Reihe von Demos und Beispielen, die auch als Ausgangspunkt für eigene erste Anwendungen genutzt werden können. Wer dazu zunächst keinen separaten C++-Compiler auf seinem Rechner installieren will, kann den im Qt-Download enthaltenen MinGW-Compiler verwenden, der eine auf die wesentlichen Bestandteile des bekannten gcc-Compiler-Systems beschränkte Version ist. Neu hinzugekommen in der Qt-Welt sind in letzter Zeit Qt Quick, mit dem UI-Anwendungen für den Embedded- und Mobile-Markt nun auch deklarativ mittels der eigenen Sprache QML erstellt werden können, und QT Mobility mit weiteren Tools für den mobilen Bereich.
Wer Qt einsetzen möchte, findet unter http://qt.nokia.com/downloads neben den bekannten Offline-Installationspaketen zum Download auch einen Online-Installer, mit dem man sofort die gesamte Bibliothek oder bei Bedarf auch nur ausgewählte Teile auf seinem Rechner installieren kann (Abb. 2). Auch ein späteres Online-Update der installierten Pakete ist hiermit möglich.

Abb. 2: Qt Online Installer

[ header = Seite 3: QT-Programmiermodell ]

QT-Programmiermodell

Listing 2 zeigt ein einfaches Hello-World-Beispiel, bei dem ein minimales GUI, bestehend aus einem einzigen Label, dargestellt wird. Zentrale Komponente einer Qt-GUI-Anwendung ist ein QApplication-Objekt, über dessen exec()-Methode ein zuvor „aufgebautes“ GUI gestartet wird. Der Aufbau des GUIs besteht in diesem Beispiel ausschließlich aus einem QLabel zur Ausgabe eines vordefiniertem Texts. Wer aus der Java-Welt kommt und dort Swing-Anwendungen erstellt hat, wird sich alleine anhand der Namensgebung bei Qt heimisch fühlen: Nicht nur QLabel, sondern auch weitere mögliche Bestandteile von Qt-GUIs wie QMenuBar, QStatusBar, QPushButton oder QDialog erinnern an die Java-Swing-Programmierung.

#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QLabel* helloWorldLabel = new QLabel("Hello Qt-World");
    helloWorldLabel->show();
    int rc = app.exec();
    delete helloWorldLabel;
    return rc;
}

Grafische Benutzeroberflächen unterscheiden sich von „normalen“ Algorithmen durch ihr zugrundeliegendes Programmiermodell. Während außerhalb eines GUI die Verarbeitung in der Regel in einer bei der Implementierung bereits festgelegten Reihenfolge erfolgt, arbeitet ein GUI ereignisorientiert. In Abhängigkeit der Benutzeraktionen (Klicken, Scrollen etc.) wird eine für ein bestimmtes Ereignis hinterlegte Funktion von der Laufzeitumgebung aufgerufen. Solche Funktionen werden auch Callback-Funktionen genannt und bilden die Reaktion auf eine Benutzeraktion.
Eine solche ereignisorientierte Programmierung liegt auch der Qt-GUI-Erstellung zugrunde. Zentrale Begriffe dabei sind Signale und Slots. Signale entsprechen dabei den beschriebenen Ereignissen und sind einem bestimmten GUI-Element zugeordnet, von dem sie ausgelöst werden. Als Reaktion auf ein solches Signal wird ein Slot, also eine bei der Implementierung festgelegte Funktion ausgelöst. Diese Qt-Besonderheiten werden über einen Qt-eigenen Präprozessor, den so genannten MOC (Meta Object Compiler), in einem vorgezogenen Compile-Schritt in „normales“ C++-Format gebracht, in dem sie dann von den gängigen Compilern weiterverarbeitet werden können.
Signale werden aber nicht nur in GUI-Komponenten verschickt und verarbeitet. Das Signale-/Slot-Konzept findet man beispielsweise auch bei der Qt-eigenen Threading-Lösung. Im zweiten Teil wurden bereits die grundlegenden Konzepte der Parallelprogrammierung mit C++ vorgestellt. Wer eine solche Multi-Threaded-Anwendung mit Qt erstellen will, findet dazu zunächst die Klasse QThread. Im Gegensatz zu anderen externen Thread-Lösungen sollte diese Klasse aber eigentlich nur dann durch eine Unterklasse erweitert werden, wenn man Funktionalität zu klassischen Betriebssystem-Threads hinzufügen möchte, was in der Praxis aber eher selten der Fall sein dürfte. Im typischen Fall einer eigenen Thread-Lösung dagegen sollte man auf den korrekten Einsatz der Methode moveToThread() aus der Klasse QObject zurückgreifen. Sie führt dazu, dass die komplette Ereignisverarbeitung der als Unterklasse von QObject realisierten Eigenlösung im korrekten eigenständigen Thread ablaufen wird. Leider ist die ansonsten sehr umfangreiche Qt-Dokumentation an dieser Stelle nicht auf dem neuesten Stand. Eine detaillierte Beschreibung der Unterschiede zwischen diesen beiden Ansätzen findet man aber auf den Qt-Webseiten.

Fazit

In diesem Artikel wurden nur Boost und Qt als C++-Bibliotheken vorgestellt. Darüber hinaus gibt es aber noch eine große Zahl von weiteren Bibliotheken. Einen guten Überblick findet man beispielsweise hier. Es müssen also nicht immer nur die großen Library-Sammlungen sein, bei denen man Hilfe für die Erstellung von C++-Programmen erhält. Für Sonderfälle wie z. B. kryptografische Lösungen oder Spieleprogrammierung bieten sich auch kleinere Speziallösungen an.
In vielen Situationen hat man aber sicher auch die Qual der Wahl. Was soll man dann nehmen? Boost oder Qt? Oder doch lieber eine Eigenentwicklung, die man dann unternehmensintern zu einem späteren Zeitpunkt für ähnliche Fragestellungen in anderen Projekten nochmals einsetzen kann? Letzteres scheidet unter Kosten-Nutzen-Aspekten meist aus: Warum soll man viel Zeit für die Erstellung einer eigenen Bibliothek investieren, wenn andere das schon früher gemacht haben und ihre Lösungen bereits in zahlreichen Projekten erfolgreich eingesetzt wurden? Die Frage nach der „besten“ Bibliothek kann nicht mit einer klaren Empfehlung beantwortet werden. Stattdessen hängt sie stark von persönlichen Präferenzen und/oder Vorgaben in Projekten und Unternehmen ab.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -