C++: Boost erfindet sich neu

Durch die zunehmende Etablierung von C++11 und C++14 sind viele Bibliotheken in Boost obsolet geworden. Zeit also für die Community, die Bibliothekensammlung neu auszurichten.

In Pocket speichern vorlesen Druckansicht 18 Kommentare lesen
Lesezeit: 12 Min.
Von
  • Dr. Rüdiger Berlich
Inhaltsverzeichnis

Durch die zunehmende Etablierung von C++11 und C++14 sind viele Bibliotheken in Boost obsolet geworden. Zeit also für die Community, die Bibliothekensammlung neu auszurichten.

Boost ist eine Sammlung hochwertiger, in einem Peer-Review-Prozess gestalteter C++-Bibliotheken. In der aktuellen Version 1.58 sind über 130 Einzelbibliotheken enthalten.

Die Boost Software License 1.0 ist bewusst Business-freundlich gehalten und umfasst nur ein schwaches Copyleft des Inhalts, das besagt, dass direkte Änderungen am Boost-Quellcode wieder unter der Boost-Lizenz stehen müssen – und das auch nur, wenn sie ihrerseits wieder als Sourcecode weitergegeben werden. Gleichzeitig ist die aktuelle Boost-Lizenz kompatibel mit den meisten anderen etablierten Open-Source-Lizenzen, insbesondere aber mit der bezüglich des "Copylefts" als streng geltenden GNU General Public License (GPU). Der Verwendung in eigenen Projekten, gleich ob proprietär oder Open Source, steht damit kaum etwas im Weg, und Einsatzfelder finden sich im gesamten Bereich der Softwareentwicklung. Viele Bibliotheken haben zudem – teils in abgeänderter Form – Eingang in den aktuellen C++14-Standard sowie seinen Vorgänger C++11 gefunden.

Genau das ist allerdings für die weitere Entwicklung von Boost ein Problem. So listet der Blog von Josh Reuben allein 23 Bibliotheken auf, die in C++11/14-Projekten ihre Bedeutung verloren haben, da diese Standards mittlerweile analoge Bibliotheken umfassen. Meist existieren diese seit Jahren und haben stets auch die Funktion erfüllt, für Nutzer den Wechsel zum eigentlich noch im letzten Jahrzehnt erwarteten und damals aus diesem Grund noch optimistisch C++0x genannten Standard möglichst zu erleichtern. Die Idee war es, Nutzern zu ermöglichen, C++0x-typische Konstrukte bereits mit C++03-Compilern zu verwenden. Ein prominentes Beispiel sind die Bibliotheken boost::shared_ptr oder boost::thread, die sich mit nur wenigen Änderungen in C++11 wiederfinden.

C++11 ist jetzt vier Jahre alt, und die meisten Konstrukte des Standards werden mittlerweile von aktuellen Compiler-Versionen unterstützt (vollständig durch GCC und Clang – diese unterstützen sogar bereits den C++14-Standard komplett), überwiegend zum Beispiel durch populäre Compiler wie Microsofts Visual C++ 2013. Es dauert naturgemäß, bis Produktionscode von C++98 auf C++11 oder gar C++14 umgestellt wird.

Mit der nun vorhandenen Reife der Compiler ist dieser Wechsel aber in vollem Gange – nicht zuletzt auch, weil C++11/14 eben auf Sprach- wie Bibliotheksebene fortgeschrittene Konstrukte enthält (z. B. Lambda-Ausdrücke, type-traits, Threads samt zugehörigem Speichermodell, std::function, Binder, Move-Semantik, auto und range-based for-Loops), ohne die moderne Programme kaum mehr denkbar sind. C++98 ist im Vergleich ein Old-Timer, im Wortsinne, und Boost verliert seine Bedeutung als Brücke zwischen altem und neuem C++.

Andere Bibliotheken, teils mit zentraler Bedeutung, werden teilweise nur noch schlecht gewartet. Für Bibliotheken mit C++11-Äquivalent mag das verständlich sein – auch die Programmierer von Boost-Bibliotheken dürften als Open-Source-Autoren eher intrinsisch motiviert sein und sich mehr für die "neuen Hüte" interessieren. Es gibt aber auch Beispiele für wichtige andere Bibliotheken ohne C++11-Äquivalent, die hier aus Respekt vor der gleichwohl großen Leistung der Autoren nicht genannt werden sollen – es gibt keinen Anspruch der Öffentlichkeit darauf, dass Privatpersonen ihre Arbeitskraft kostenlos zur Verfügung stellen. Die Nutzer einer Bibliothek können aber gleichwohl unter fehlenden Beiträgen leiden, und damit könnte die Popularität von Boost insgesamt sinken.

Hierzu trägt sicherlich auch bei, dass die Dokumentation mancher Boost-Bibliotheken zu wünschen übrig lässt – hierzu weiter unten mehr. Auch das eigene, recht kryptische Build-System Boost.Build hilft nicht – das im Open-Source-Umfeld mittlerweile verbreitete CMake könnte hier was ändern. Entsprechende Anstrengungen im Boost-Lager scheinen aber nur langsam voranzugehen.

Boost steckt also in einer Art Sinnkrise. Auf den Boost-Mailinglisten finden derzeit hierzu häufige Diskussionen statt – und genau das ist ja wieder ein gutes Zeichen.

Ebenfalls nicht trivial ist der Versuch, aus der ja gewollten Nähe vieler Boost-Bibliotheken zu C++11 Profit zu schlagen bei der Konvertierung eigenen Codes zum neuen Standard. Am Beispiel einer rund 130.000 Zeilen Code umfassenden Open-Source-Bibliothek zur technisch-wissenschaftlichen Optimierung (Geneva) hat der Autor das nachvollzogen. Sie hat Boost als einzige externe Abhängigkeit und sich seit Beginn der Entwicklung stark auf Boost gestützt.

Um den Artikel nicht zu überfrachten, soll hier nur der nach außen als trivial erscheinende Wechsel von boost::shared_ptr, boost::bind und boost::thread zu ihren C++11-Äquivalenten diskutiert werden. Die Annahme hierbei war, dass ein einfaches Refactoring mit einem Wechsel des Namespaces von boost zu std und der Eliminierung nun nicht mehr benötigter Header ausreichen sollte. Die C++11-APIs sind schließlich dank der Boost-Provenienz weitgehend identisch. Aber eben auch nur weitgehend – der Versuch, mit ein paar grep-/sed-Aufrufen den Wechsel zu vollziehen, führte zunächst ins Chaos.

So enthält std::thread zwar fast alle boost::thread-Funktionen (samt zugehöriger Funktionalität aus mutexes oder condition-Variablen), ihr fehlt aber der in Geneva häufig verwendete interrupt()-Call,
mit dem einem Thread signalisiert werden kann, dass er seine Ausführung beenden soll. Es ist dann Aufgabe der durch den Thread ausgeführten Funktion, sich an einer passenden Stelle zu beenden. Das Nachprogrammieren ist dabei nicht trivial.

Der Wechsel zu std::shared_ptr war im Vergleich einfacher, erforderte aber auch eine Änderung in der Geneva-API, um die gemischte Verwendung von den ineinander nur auf Umwegen konvertierbaren Smartpointer-Implementierungen zu vermeiden. Das Ziel war ja die komplette Eliminierung von boost::shared_ptr aus dem Code. Der Hintergrund lag ausgerechnet in einer Boost-Bibliothek zum Parsen von Konfigurationsdateien und Kommandozeilen-Optionen (Boost.Program_Options), die bislang keine std::shared_ptr auf von ihr erwartete Objekte akzeptiert. Das Geneva-Interface musste damit so verändert werden, dass nur solche Teile der Boost.Program_Options-API für den Nutzer sichtbar sind, die keine boost::shared_ptr erwarten. Wobei nebenbei gemerkt das Interface schlanker und logischer wurde, ohne Funktionen einzubüßen.

Hinweisen muss man im Zusammenhang mit boost::shared_ptr auch auf das folgende Problem: Es handelt sich um eine Header-only-Bibliothek. Enthalten solche aber auf dem Weg der bedingten Übersetzung zusätzliche API-Komponenten im Falle der C++11-Übersetzung (z. B. einen Move-Konstruktor), kann es zu Problemen kommen. C++11 ist zwar im Prinzip rückwärtskompatibel, und C++11-Code kann durchaus mit C++98-Bibliotheken verlinkt werden. Wird aus diesen aber zum Beispiel ein boost::shared_ptr an einen C++11-Aufrufer herausgereicht, könnte es zu einer Verletzung der "One Definition Rule" kommen. Auch aus diesem Grund erschien es den Geneva-Autoren wichtig, die Verwendung von boost::shared_ptr komplett zu eliminieren.

Abgeschlossen ist der Wechsel zu std::shared_ptr in Geneva im Übrigen noch nicht, denn erst neuere Boost-Versionen erlauben die Serialisierung von std::shared_ptr. Da Geneva aber auch ältere Boost-Versionen (bis 1.53) unterstützen möchte, um aktuelle Linux-Distributionen bedienen zu können, muss ein Weg gefunden werden, auch mit solchen "inkompatiblen" Boost-Versionen transparent zu serialisieren.

Bei std::bind gab es zu guter Letzt Kompatibilitätsprobleme mit einigen von boost::bind akzeptierten Konstrukten. Das Problem ließ sich jedoch vollständig durch die Verwendung der C++11-Lambda-Ausdrücke umgehen – wovon der Geneva-Code wieder profitierte.

Der Wechsel zu C++11 war insofern im Hinblick auf die Eliminierung einiger Boost-Bibliotheken erheblich komplexer als erwartet. Insbesondere zwingen nicht vollständig auf C++11 abgebildete APIs zu Umwegen, wie im Fall der program_options. Gerechterweise ist aber zu sagen, dass Geneva von der Möglichkeit der Verwendung von C++11-Konstrukten lange vor dem Release des Standards stark profitiert hat. Und dass der C++-Standard einige aus Sicht des Autors wichtige Konstrukte nicht unterstützt, ist Boost nicht anzulasten.