Die Boost C++ Bibliotheken

Kapitel 69. Boost.Utility

Die Bibliothek Boost.Utility ist ein Sammelsurium verschiedener Klassen und Funktionen, die sich als sehr nützlich herausgestellt haben, jedoch zu klein sind, um in eigenen Bibliotheken gepflegt zu werden. Da die von Boost.Utility angebotenen Hilfsmittel so klein sind, lassen sie sich schnell erlernen. Weil Boost.Utility jedoch ein Sammelbecken für alles Mögliche ist, hängen die Hilfsmittel in keiner Weise zusammen.

Wie in diesem Buch üblich soll anhand von Beispielen vorgestellt werden, was die Bibliothek zu bieten hat. Weil sich in Boost.Utility verschiedenste und voneinander unabhängige Hilfsmittel befinden, bauen die folgenden Beispiele jedoch nicht aufeinander auf.

Boost.Utility stellt mit boost/utility.hpp eine Headerdatei zur Verfügung, über die auf verschiedene Hilfsmittel zugegriffen werden kann. Da jedoch nicht alle Hilfsmittel über boost/utility.hpp eingebunden werden, wird in den folgenden Beispielprogrammen jeweils auf genau die Headerdatei zugegriffen, in der das jeweilige Hilfsmittel definiert ist.

Anmerkung

Mit Boost 1.55.0 wurde eine neue Bibliothek namens Boost.Core eingeführt, in die einige Hilfsmittel aus Boost.Utility übertragen wurden. So befindet sich zum Beispiel boost::checked_delete() seit dieser Version in Boost.Core. Da es sich lediglich um eine Reorganisation in den Boost-Bibliotheken handelt, haben sich weder die Hilfsmittel an sich noch die Headerdateien, in denen sie sich befinden, geändert. So können Sie die Beispiele in diesem Kapitel auch mit Boost 1.55.0 oder einer neueren Version kompilieren.

Beispiel 69.1. boost::checked_delete() in Aktion
#include <boost/checked_delete.hpp>
#include <boost/intrusive/list.hpp>
#include <string>
#include <utility>
#include <iostream>

struct animal : public boost::intrusive::list_base_hook<>
{
  std::string name_;
  int legs_;

  animal(std::string name, int legs) : name_{std::move(name)},
    legs_{legs} {}
};

int main()
{
  animal *a = new animal{"cat", 4};

  typedef boost::intrusive::list<animal> animal_list;
  animal_list al;

  al.push_back(*a);

  al.pop_back_and_dispose(boost::checked_delete<animal>);
  std::cout << al.size() << '\n';
}

Im Beispiel 69.1 wird eine Funktion boost::checked_delete() als Parameter an die Methode pop_back_and_dispose() übergeben, die die in der Bibliothek Boost.Intrusive definierte Klasse boost::intrusive::list zur Verfügung stellt. Während boost::intrusive::list und pop_back_and_dispose() im Kapitel 18 vorgestellt werden, handelt es sich bei boost::checked_delete() um eine von Boost.Utility zur Verfügung gestellte Funktion. Um diese Funktion nutzen zu können, muss die Headerdatei boost/checked_delete.hpp eingebunden werden.

boost::checked_delete() erwartet als einzigen Parameter einen Zeiger auf ein Objekt, das mit delete zerstört wird. Da an pop_back_and_dispose() eine Funktion übergeben werden muss, die einen Zeiger als einzigen Parameter erwartet, um das entsprechende Objekt zu zerstören, bietet es sich an, auf die von Boost.Utility zur Verfügung gestellte Funktion zuzugreifen. So muss die Funktion nicht selbst definiert werden.

Der Unterschied zwischen boost::checked_delete() und einer selbst definierten Funktion, die lediglich auf delete zugreift, ist: boost::checked_delete() stellt sicher, dass der Typ des Objekts, das zerstört wird, vollständig ist. delete hingegen akzeptiert auch Zeiger auf Objekte, deren Typ nicht vollständig ist. Es handelt sich hierbei um ein Detail aus dem C++-Standard, das Sie ignorieren können, wenn es Ihnen nicht bekannt ist. Der Vollständigkeit halber muss jedoch erwähnt werden, dass boost::checked_delete() nicht völlig identisch ist mit einem Aufruf von delete, sondern etwas höhere Anforderungen stellt.

Neben boost::checked_delete() stellt Boost.Utility boost::checked_array_delete() zur Verfügung. Diese Funktion muss verwendet werden, wenn ein Array zerstört werden soll. Sie ruft delete[] statt delete auf. Darüber hinaus stehen mit boost::checked_deleter und boost::checked_array_deleter zwei Klassen zur Verfügung, um Funktionsobjekte zu erstellen, die sich so verhalten wie boost::checked_delete() und boost::checked_array_delete().

Beispiel 69.2. BOOST_CURRENT_FUNCTION in Aktion
#include <boost/current_function.hpp>
#include <iostream>

int main()
{
  const char *funcname = BOOST_CURRENT_FUNCTION;
  std::cout << funcname << '\n';
}

Im Beispiel 69.2 wird ein Makro namens BOOST_CURRENT_FUNCTION verwendet, mit dem der Name einer Funktion als Zeichenkette erhalten werden kann – und zwar der Funktion, in der sich das Makro befindet. Dazu muss die Headerdatei boost/current_function.hpp eingebunden werden.

BOOST_CURRENT_FUNCTION stellt eine plattformunabhängige Möglichkeit dar, den Namen einer Funktion zu erhalten. Seit C++11 können Sie auf das standardisierte Makro __func__ zugreifen. Vor C++11 boten Compiler wie Visual C++ oder GCC das Makro __FUNCTION__ an. Da es sich hierbei um eine Erweiterung dieser Compiler handelte und nicht um ein Makro, das im Standard definiert war, half BOOST_CURRENT_FUNCTION, plattformunabhängigen Code zu schreiben.

Beispiel 69.2 unter Windows ausgeführt und mit Visual C++ 2013 übersetzt gibt int __cdecl main(void) aus.

Beispiel 69.3. boost::prior() und boost::next() in Aktion
#include <boost/next_prior.hpp>
#include <array>
#include <algorithm>
#include <iostream>

int main()
{
  std::array<char, 4> a{{'a', 'c', 'b', 'd'}};

  auto it = std::find(a.begin(), a.end(), 'b');
  auto prior = boost::prior(it, 2);
  auto next = boost::next(it);

  std::cout << *prior << '\n';
  std::cout << *it << '\n';
  std::cout << *next << '\n';
}

Mit boost::prior() und boost::next() stellt Boost.Utility zwei einfache Funktionen zur Verfügung, um einen Iterator relativ zu einem anderen zu erhalten. Während it im Beispiel 69.3 auf den Buchstaben b im Array zeigt, wird mit prior auf a und mit next auf d verwiesen.

Der Unterschied zwischen boost::prior() und boost::next() auf der einen und std::advance() auf der anderen Seite ist, dass die von Boost.Utility angebotenen Funktionen den Iterator, der als Parameter übergeben wird, nicht verändern. Stattdessen geben Sie einen neuen Iterator zurück.

Beide Funktionen boost::prior() und boost::next() akzeptieren neben einem Iterator einen zweiten Parameter. Dieser Parameter gibt an, wie viele Schritte der Iterator vor- oder zurückspringen soll. Während boost::prior() im Beispiel 69.3 den Iterator zwei Schritte nach hinten setzt, führt der Aufruf von boost::next() dazu, dass der Iterator einen Schritt nach vorne macht.

Beachten Sie, dass Sie bei Angabe der Schritte in jedem Fall eine positive Zahl angeben müssen – auch dann, wenn Sie mit boost::prior() Schritte nach hinten machen.

Um boost::prior() und boost::next() nutzen zu können, müssen Sie die Headerdatei boost/next_prior.hpp einbinden.

Die beiden Funktionen sind mit C++11 in die Standardbibliothek aufgenommen worden. Sie heißen dort std::prev() und std::next() und sind in der Headerdatei iterator definiert.

Beispiel 69.4. boost::noncopyable in Aktion
#include <boost/noncopyable.hpp>
#include <string>
#include <utility>
#include <iostream>

struct animal : boost::noncopyable
{
  std::string name;
  int legs;

  animal(std::string n, int l) : name{std::move(n)}, legs{l} {}
};

void print(const animal &a)
{
  std::cout << a.name << '\n';
  std::cout << a.legs << '\n';
}

int main()
{
  animal a{"cat", 4};
  print(a);
}

Boost.Utility bietet eine Klasse boost::noncopyable an, die verhindert, dass Objekte kopiert werden können. Dazu muss lediglich von boost::noncopyable abgeleitet werden. Diese Klasse ist in der Headerdatei boost/noncopyable.hpp definiert.

Der Effekt ist der Gleiche, wie wenn der Copy-Konstruktor und der Zuweisungsoperator als private Methoden definiert werden oder – was seit C++11 möglich ist – mit delete explizit aus einer Klasse entfernt werden. Wird boost::noncopyable als Elternklasse verwendet, kann jedoch unter Umständen die Lesbarkeit des Codes verbessert werden, da deutlicher erkennbar ist, dass Objekte einer Klasse nicht kopiert werden dürfen.

Anmerkung

Einige Entwickler ziehen boost::noncopyable vor, andere explizit mit delete entfernte Methoden. Sie finden zum Beispiel auf Stack Overflow Argumente beider Seiten.

Beispiel 69.4 kann kompiliert und ausgeführt werden. Würde jedoch der Funktionskopf von print() geändert werden, so dass der Parameter keine Referenz mehr wäre, sondern ein Objekt vom Typ animal als Kopie erwartet werden würde, würde der Compiler einen Fehler melden.

Beispiel 69.5. boost::addressof() in Aktion
#include <boost/utility/addressof.hpp>
#include <string>
#include <iostream>

struct animal
{
  std::string name;
  int legs;

  int operator&() const { return legs; }
};

int main()
{
  animal a{"cat", 4};
  std::cout << &a << '\n';
  std::cout << boost::addressof(a) << '\n';
}

Mit boost::addressof() stellt Boost.Utility eine Funktion zur Verfügung, um die Adresse eines Objekts zu erhalten – auch dann, wenn wie im Beispiel 69.5 der Operator operator& überladen wurde.

Um boost::addressof() einsetzen zu können, müssen Sie die Headerdatei boost/utility/addressof.hpp einbinden.

Die Funktion ist mit C++11 in die Standardbibliothek aufgenommen wurden und steht als std::addressof() in der Headerdatei memory zur Verfügung.

Beispiel 69.6. BOOST_BINARY in Aktion
#include <boost/utility/binary.hpp>
#include <iostream>

int main()
{
  int i = BOOST_BINARY(1001 0001);
  std::cout << i << '\n';

  short s = BOOST_BINARY(1000 0000 0000 0000);
  std::cout << s << '\n';
}

Boost.Utility bietet mit BOOST_BINARY ein Makro an, um Zahlen im Binärformat anzugeben. Denn während mit Präfixen wie 0x und 0 Zahlen im Hexadezimal- und Oktalformat angegeben werden konnten, unterstützte C++ lange kein Binärformat. Das hat sich erst mit C++14 geändert, das das Präfix 0b kennt.

Beispiel 69.6 gibt 145 und -32768 aus. Die zweite Zahl ist negativ, weil s auf dem 16-Bit-Typ short basiert und das 16. Bit – das höchstwertige Bit in short – auf 1 gesetzt wird. Da dieses Bit das Vorzeichen darstellt, wird in s eine Bitfolge gespeichert, die eine negative Zahl darstellt.

BOOST_BINARY stellt letztendlich nur eine andere Möglichkeit dar, Zahlen anzugeben. Da Zahlen in C++ standardmäßig den Typ int haben, entspricht auch BOOST_BINARY einem int. Möchten Sie beispielsweise eine Zahl vom Typ long definieren, können Sie auf das Makro BOOST_BINARY_L zugreifen. Dies entspricht einer Zahl mit dem Suffix L.

Boost.Utility stellt weitere Makros wie BOOST_BINARY_U zur Verfügung, um eine Variable ohne Vorzeichenbit zu initialisieren. Dieses ist wie alle anderen Makros in der Headerdatei boost/utility/binary.hpp definiert.

Beispiel 69.7. boost::string_ref in Aktion
#include <boost/utility/string_ref.hpp>
#include <iostream>

boost::string_ref start_at_boost(boost::string_ref s)
{
  auto idx = s.find("Boost");
  return (idx != boost::string_ref::npos) ? s.substr(idx) : "";
}

int main()
{
  boost::string_ref s = "The Boost C++ Libraries";
  std::cout << start_at_boost(s) << '\n';
}

Beispiel 69.7 stellt die Klasse boost::string_ref vor. boost::string_ref ist eine Referenz auf einen String, die lediglich einen lesenden Zugriff erlaubt. Sie ist in gewisser Weise vergleichbar mit const std::string&. const std::string& setzt jedoch voraus, dass ein String vom Typ std::string existiert. boost::string_ref kann auch ohne std::string verwendet werden. Der entscheidende Vorteil von boost::string_ref ist, dass auf Speicherallokationen verzichtet werden kann, wie sie bei einem vielfältigen Einsatz von std::string nötig wären.

Beispiel 69.7 sucht in einem String nach dem Wort Boost. Wird es gefunden, wird ein String beginnend mit diesem Wort ausgegeben. Wird Boost nicht gefunden, wird ein leerer String ausgegeben. Der String s in der Funktion main() erhält jedoch nicht den Typ std::string, sondern boost::string_ref. Es findet daher keine Speicherallokation mit new statt. Es wird keine Kopie erstellt, wie es std::string tun würde. Stattdessen verweist s direkt auf die Zeichenkette The Boost C++ Libraries.

Der Typ des Rückgabewerts von start_at_boost() lautet ebenfalls nicht std::string, sondern boost::string_ref. Die Funktion gibt keinen neuen String zurück, sondern eine Referenz. Der von start_at_boost() zurückgegebene Wert ist entweder ein Substring des Parameters, der an die Funktion übergeben wurde, oder eine leere Zeichenkette. Die Funktionsweise von start_at_boost() setzt voraus, dass der ursprüngliche String mindestens so lange gültig ist wie die Referenzen vom Typ boost::string_ref, die auf ihn verweisen. Ist dies wie im obigen Beispiel gewährleistet, können Speicherallokationen, wie sie von std::string typischerweise vorgenommen werden, vermieden werden.

Boost.Utility stellt einige weitere Hilfsmittel zur Verfügung, auf die in diesem Buch nicht näher eingegangen wird. Es handelt sich hierbei vorwiegend um Hilfsmittel, die sich entweder speziell an Entwickler von Boost-Bibliotheken richten oder in der Template-Metaprogrammierung zum Einsatz kommen. Die Dokumentation von Boost.Utility kann Ihnen einen Überblick über diese weiteren Hilfsmittel verschaffen.