iX 3/2016
S. 42
Titel
C++-Standard
Aufmacherbild

Wie Boost sich regelmäßig neu erfindet

Im Umbruch

Viele Funktionen des aktuellen C++14 existieren schon länger als Teil der Bibliothekssammlung Boost. Durch die Aufnahme in den Standard verlieren die früheren Implementierungen häufig ihre Bedeutung. Dadurch stellt Boost einerseits wichtige Weichen für C++, muss aber andererseits ständig in Bewegung bleiben.

Boost hat aktuellen C++-Standards den Weg geebnet und eine frühzeitige Verwendung vieler C++11/14-Features vor der Implementierung in den Compilern und Standardbibliotheken ermöglicht. Welche Gemeinsamkeiten und Unterschiede bestehen zwischen ausgewählten Boost-Bibliotheken sowie ihren C++11/14-Pendants? Was kann man für C++17 erwarten?

Spätestens mit dem Update auf Visual Studio 2015 verfügen alle wichtigen Compiler über eine (fast) vollständige Unterstützung des C++11-Standards. Für C++14 besteht ein zumindest zufriedenstellender Support [a, b] (Onlinereferenzen finden sich unter „Alle Links“ am Ende des Artikels). Hinsichtlich ihrer Standardkonformität führen Clang und g++ das Feld an. Der Vorsprung dürfte jedoch schrumpfen. Dank der angekündigten Interoperabilität von Microsoft Visual C++ mit Clang [c] und der beginnenden Portierung von Visual Studio auf Linux und OS X [d] könnte portabler C++-Code künftig die Regel darstellen statt wie bisher die Ausnahme. Microsoft gebührt für dieses konstruktive Vorgehen besonderer Dank, und die Migration von Legacy-Code auf aktuelle Standards dürfte erheblich an Geschwindigkeit gewinnen.

Wer frühzeitig auf die heute mehr als 130 Boost-Bibliotheken gesetzt hat, verwendet wahrscheinlich bereits viele Fähigkeiten von C++11/14 aktiv im Code. Und wenn man noch auf ältere Compiler angewiesen ist, lässt sich die spätere Migration mit Boost vereinfachen. Deshalb sollen ausgewählte Boost-Bibliotheken ihren C++11/14-Äquivalenten gegenübergestellt und ein Ausblick auf C++17 gewagt werden. Bekanntlich liegt der Teufel im Detail: Einige Schwierigkeiten bei der Migration von Boost auf C++11/14 verdienen eine genauere Betrachtung.

Dem Standard einen Schritt voraus

C++11 enthält im Vergleich zur vorherigen Überarbeitung, die den Namen C++03 trägt, sowie dem Vorgängerstandard C++98 viele Neuerungen – bemerkenswert für eine Sprache mit einer derartigen Verbreitung [e]. Der Schritt zu C++14 fällt im Vergleich deutlich kleiner aus [f]. Die Änderungen betreffen sowohl die Sprachebene als auch die Standardbibliothek. Bezüglich Letzterer nahm der (allerdings eher inoffizielle) Technical Report 1 (TR1) aus dem Jahr 2007 Teile der neuen Standards vorweg. Dadurch hielten diese in vielen Compilern bereits Einzug, wurden aber eben noch nicht Teil des Standards. Boost implementierte frühzeitig mit Boost.TR1 den Report vollständig, jedoch nicht im Namespace std, sondern in std::tr1. Diese Implementierung stellt einen Wrapper dar, der, wann immer möglich, auf die jeweilige Standardimplementierung zurückgreift, fehlende Klassen und Funktionen aber durch ihr jeweiliges Boost-Äquivalent ersetzt.

Für viele Boost- wie auch C++11-Nutzer mit einem C++98-Migrationshintergrund stellen Smart-Pointer den ersten Kontakt mit den neuen Techniken dar, ergänzt durch eine Auswahl anderer Bibliotheken mit fast instantanem Mehrwert. boost::shared_ptr, reguläre Ausdrücke, Binder, Zufallszahlen und -verteilungen oder Type Traits (verbunden mit enable_if) sollten heute in keinem C++11/14-Programm fehlen und wurden durch TR1 sowie seine Boost-Implementierung vorweggenommen. Schwierigkeiten beim Umstieg von diesen Boost-Bibliotheken auf C++11/14 sollten kaum auftreten, sofern nicht Boost-spezifische Erweiterungen zum Einsatz kamen. Ein Beispiel: Für Boosts bind-Ausdrücke existieren Vergleichsoperatoren – gerne verwendet beim Sortieren von Standardvektoren aus Smart-Pointern. Der C++-Standard bietet dies nicht. Standardkonform lässt sich Vergleichbares jedoch oft sogar einfacher mit den seit C++11 verfügbaren Lambda-Ausdrücken realisieren.

Unveränderte Logik in Programmen

Listing 1: Füllen eines boost::array mit boost::random, Sortieren mittels boost::bind

#include <iostream>
#include <algorithm>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/array.hpp>
#include <boost/random.hpp>

class secret {
public:
  secret(int s) :secret_(s)
  { /* nothing */ }

  int value() const {
    return secret_;
  }

private:
  secret() = delete;
  int secret_ = 0;
};

const std::size_t ARRAYSIZE=10;

int main(int argc, char **argv) { 
  boost::random::mt19937 generator;
  boost::random::uniform_int_distribution<> 
 wuerfel(1, 6); 

  boost::array<boost::shared_ptr<secret>,
 ARRAYSIZE> v_secrets;
  for(std::size_t pos=0; pos<ARRAYSIZE;
  pos++) {
    v_secrets.at(pos)=
 boost::shared_ptr<secret>(new secret
 (wuerfel(generator)));
  }

  // Sortierung nach den "secret"-Werten. 
  // boost::shared_ptr wird automatisch 
  // berücksichtigt.
  auto withBind = boost::bind(&secret::value, 
 _1) < boost::bind(&secret::value,_2);
  std::sort(v_secrets.begin(), 
            v_secrets.end(), 
            withBind);

  for(auto s: v_secrets) { 
    std::cout << s->value() << std::endl; 
  }
}

Listing 1 zeigt eine einfache Applikation, die zunächst ein boost::array (ein Array statischer Größe mit Zugriffsmöglichkeiten analog std::vector) mit Smart-Pointern auf eine einfache Klasse secret füllt. secret enthält einen mittels Boost.Random zufällig generierten Integer-Wert, den die Methode value() ausgeben kann. Das Sortieren übernimmt die Funktion sort() aus der Standardbibliothek. Das für die Sortierung nötige Funktionsobjekt vergleicht zwei boost::bind-Objekte. _1 und _2 stellen Platzhalter dar, die durch den jeweiligen Wert im boost::array ersetzt werden. boost::bind berücksichtigt automatisch die boost::shared_ptr. Alternativen in Boost zur dynamischen Erzeugung solcher Funktionsobjekte stellen die Bibliotheken Boost.Lambda und Boost.Phoenix dar.

Listing 2: Das Analogon zu Listing 1 mit den Mitteln von C++11/14 (nur relevante Änderungen werden gezeigt)

int main(int argc, char **argv) {
  // Ggf. fuer std::bind benötigt
  // using namespace std::placeholders;

  std::mt19937 generator;
  std::uniform_int_distribution<int> 
 wuerfel(1,6);

  std::array<std::shared_ptr<secret>,
 ARRAYSIZE> v_secrets;
  for(std::size_t pos=0; pos<ARRAYSIZE; 
 pos++) {
    v_secrets.at(pos)=std::shared_ptr
 <secret>(new secret(wuerfel(generator)));
  }
  // Sortierung nach den "secret"-Werten
  auto lambda = [](std::shared_ptr<secret> x, 
 std::shared_ptr<secret> y) -> bool {
        return x->value() < y->value();
  };

  std::sort(v_secrets.begin(),
            v_secrets.end(),
            lambda);

  for(auto s: v_secrets) { 
    std::cout << s->value() << std::endl; 
  }

}

Listing 2 zeigt das analoge Programm, dieses Mal allein mit den Mitteln von C++11/14. Im Vergleich zu Listing 1 identische Teile werden nicht aufgeführt. std::random und Boost.Random folgen denselben Konzepten, teilweise bis hinunter zu den Klassennamen. Bei mt19937 handelt es sich beispielsweise um ein Typedef für eine Instanziierung eines Mersenne-Twister-Algorithmus mit ausgewählten Parametern. In der Standardimplementierung finden sich allerdings zusätzlich einige andere Verteilungen und Generatoren.