Die Boost C++ Bibliotheken

Kapitel 55. Boost.System

Boost.System ist eine Bibliothek, die im Wesentlichen vier Klassen zur Identifikation von Laufzeitfehlern zur Verfügung stellt. Alle vier Klassen sind mit C++11 in die Standardbibliothek aufgenommen worden, so dass Sie in einer Entwicklungsumgebung, die C++11 unterstützt, Boost.System nicht verwenden müssen. Viele Boost-Bibliotheken greifen jedoch auf Boost.System zu, so dass Sie unter Umständen über andere Bibliotheken mit Boost.System in Berührung kommen.

Die einfachste Klasse ist boost::system::error_code, die betriebssystemspezifische Fehler repräsentiert. Da Betriebssysteme typischerweise Fehler durchnummerieren, speichert boost::system::error_code in einer int-Variablen einen Fehlercode. Die Klasse wird im Beispiel 55.1 verwendet, um einem Aufrufer mitzuteilen, wenn eine Funktion fehlschlägt.

Beispiel 55.1. boost::system::error_code in Aktion
#include <boost/system/error_code.hpp>
#include <iostream>

using namespace boost::system;

void fail(error_code &ec)
{
  ec = errc::make_error_code(errc::not_supported);
}

int main()
{
  error_code ec;
  fail(ec);
  std::cout << ec.value() << '\n';
}

Beispiel 55.1 definiert eine Funktion fail(), deren einzige Aufgabe es ist, einen Fehler zurückzugeben. Damit der Aufrufer von fail() erfährt, wenn die Funktion fehlschlägt, übergibt er als Parameter ein Objekt vom Typ boost::system::error_code. Der Parameter wird als Referenz übergeben, damit der Aufrufer überprüfen kann, ob fail() einen Fehler zugewiesen hat. Viele Funktionen, die von Boost-Bibliotheken angeboten werden und boost::system::error_code verwenden, sehen ähnlich aus. So bietet zum Beispiel Boost.Asio eine Funktion boost::asio::ip::host_name() an, der ein Objekt vom Typ boost::system::error_code übergeben werden kann.

Boost.System definiert zahlreiche Fehlercodes im Namensraum boost::system::errc. Im Beispiel 55.1 wird auf den Fehlercode boost::system::errc::not_supported zugegriffen, um ihn dem Parameter ec zuzuweisen. Da boost::system::errc::not_supported eine Zahl ist, ec jedoch ein Objekt vom Typ boost::system::error_code, wird die Funktion boost::system::errc::make_error_code() aufgerufen. Sie erstellt ein Objekt vom Typ boost::system::error_code mit dem entsprechenden Fehlercode.

In main() wird für ec die Methode value() aufgerufen. Sie gibt die im Objekt gespeicherte Zahl zurück.

0 bedeutet standardmäßig, dass kein Fehler vorliegt. Jeder andere Wert weist auf einen Fehler hin. Welche Bedeutung der entsprechende Fehlercode hat, hängt vom Betriebssystem ab. Sie müssen in der Dokumentation Ihres Betriebssystems nachschlagen, welchen Fehler welcher Code beschreibt.

Neben value() bietet die Klasse boost::system::error_code eine Methode category() an. Diese führt zur zweiten Klasse, die Boost.System definiert: category() gibt ein Objekt vom Typ boost::system::error_category zurück.

Fehlercodes sind wie bereits erfahren einfach nur Zahlen. Während ein Betriebssystemhersteller wie Microsoft darauf achten kann, dass Zahlen jeweils eindeutig einen Betriebssystemfehler repräsentieren, wird es für Anwendungsentwickler bedeutend schwieriger sicherzustellen, dass Fehlercodes quer über alle existierenden Programme hinweg einmalig sind. Es müsste eine Datenbank geben, in der alle Softwareentwickler die von ihnen benötigten Fehlercodes eintragen, so dass andere Softwareentwickler nicht die gleichen Fehlercodes für ganz andere Problemfälle verwenden. Da derartiges nicht praktikabel ist, gibt es Fehlerkategorien.

Fehler vom Typ boost::system::error_code gehören einer Fehlerkategorie an, die über die Methode category() erhalten werden kann. Wird ein Fehler mit boost::system::errc::make_error_code() erstellt, gehört er automatisch der generischen Kategorie an. Das ist die Kategorie, in die Fehler fallen, die nicht explizit einer anderen Kategorie zugewiesen wurden.

Beispiel 55.2. boost::system::error_category in Aktion
#include <boost/system/error_code.hpp>
#include <iostream>

using namespace boost::system;

void fail(error_code &ec)
{
  ec = errc::make_error_code(errc::not_supported);
}

int main()
{
  error_code ec;
  fail(ec);
  std::cout << ec.value() << '\n';
  std::cout << ec.category().name() << '\n';
}

Wenn Sie wie im Beispiel 55.2 category() aufrufen, erhalten Sie die zum Fehler gehörende Kategorie. Es handelt sich hierbei um ein Objekt vom Typ boost::system::error_category. Es bietet nur sehr wenige Methoden an. Sie können zum Beispiel name() aufrufen, um den Namen der Kategorie zu erhalten. So gibt Beispiel 55.2 generic auf die Standardausgabe aus.

Sie können auf die generische Kategorie auch über die freistehende Funktion boost::system::generic_category() zugreifen.

Boost.System bietet eine zweite Kategorie an: Betriebssystemfehler gehören der Kategorie System an – eine Referenz auf ein entsprechendes Objekt wird von der freistehenden Funktion boost::system::system_category() zurückgegeben. Geben Sie den Namen dieser Kategorie auf den Bildschirm aus, lesen Sie system.

Fehler sind über den Fehlercode und die Fehlerkategorie eindeutig identifizierbar. Weil Fehlercodes lediglich pro Kategorie eindeutig sein müssen, sollten Sie, wenn Sie für Ihr eigenes Programm Fehlercodes definieren wollen, eine neue Kategorie erstellen. So können Sie Fehlercodes beliebig vergeben, ohne dass es versehentlich zu Überschneidungen mit Fehlercodes anderer Entwickler kommt.

Beispiel 55.3. Eigene Fehlerkategorien erstellen
#include <boost/system/error_code.hpp>
#include <string>
#include <iostream>

class application_category :
  public boost::system::error_category
{
public:
  const char *name() const noexcept { return "my app"; }
  std::string message(int ev) const { return "error message"; }
};

application_category cat;

int main()
{
  boost::system::error_code ec{129, cat};
  std::cout << ec.value() << '\n';
  std::cout << ec.category().name() << '\n';
}

Um eine eigene Fehlerkategorie zu definieren, müssen Sie eine neue Klasse erstellen und diese von boost::system::error_category ableiten. Dies erfordert, verschiedene Methoden zu implementieren, die durch die Schnittstelle von boost::system::error_category vorgegeben sind. Sie müssen auf alle Fälle zwei Methoden name() und message() definieren, wenn Sie verhindern wollen, dass Ihre Klasse abstrakt wird, da diese Methoden als rein virtuelle Methoden in der Klasse boost::system::error_category definiert sind. Andere Methoden können, müssen aber nicht definiert werden, da boost::system::error_category Standarddefinitionen enthält.

Während name() den Namen der Fehlerkategorie zurückgibt, wird message() aufgerufen, um für einen Fehlercode eine Fehlerbeschreibung zu erhalten. Üblicherweise wird dabei im Gegensatz zu Beispiel 55.3 der Parameter ev ausgewertet und je nach Fehlercode eine andere Fehlerbeschreibung zurückgegeben.

Sie können ein Objekt vom Typ der neu definierten Fehlerkategorie verwenden, um mit ihm einen Fehler zu initialisieren. So wird im Beispiel 55.3 der Fehler ec mit der neuen Fehlerkategorie application_category erstellt. Der Fehlercode 129 ist demnach kein generischer Fehler, sondern besitzt eine Bedeutung, die vom Entwickler der Fehlerkategorie festgelegt wurde.

Anmerkung

Um Beispiel 55.3 mit Visual C++ 2013 zu kompilieren, entfernen Sie das Schlüsselwort noexcept. Diese Version des Microsoft-Compilers unterstützt noexcept noch nicht.

Die Klasse boost::system::error_code besitzt eine Methode default_error_condition(), die zur nächsten Klasse führt, die Boost.System anbietet. Diese Methode gibt ein Objekt vom Typ boost::system::error_condition zurück. Dabei handelt es sich um eine Klasse, deren Schnittstelle fast identisch mit der von boost::system::error_code ist. Der einzige Unterschied ist die soeben erwähnte Methode default_error_condition(), die ausschließlich von boost::system::error_code zur Verfügung gestellt wird.

Beispiel 55.4. boost::system::error_condition in Aktion
#include <boost/system/error_code.hpp>
#include <iostream>

using namespace boost::system;

void fail(error_code &ec)
{
  ec = errc::make_error_code(errc::not_supported);
}

int main()
{
  error_code ec;
  fail(ec);
  boost::system::error_condition ecnd = ec.default_error_condition();
  std::cout << ecnd.value() << '\n';
  std::cout << ecnd.category().name() << '\n';
}

boost::system::error_condition sieht der Klasse boost::system::error_code zum Verwechseln ähnlich und wird genauso verwendet. So können Sie wie im Beispiel 55.4 auch für boost::system::error_condition die Methoden value() und category() aufrufen.

Während die Klasse boost::system::error_code für plattformabhängige Fehlercodes verwendet wird, wird für plattformunabhängige Fehlercodes auf boost::system::error_condition zugegriffen. Indem Sie die Methode default_error_condition() aufrufen, wird ein plattformabhängiger Fehlercode in einen plattformunabhängigen vom Typ boost::system::error_condition umgewandelt.

Sie können boost::system::error_condition verwenden, wenn Sie Fehler identifizieren möchten, die plattformübergreifend sind. Das kann zum Beispiel ein fehlgeschlagener Zugriff auf eine nicht-existierende Datei sein. Während Betriebssysteme unterschiedliche Schnittstellen zum Zugriff auf Dateien anbieten können und die Fehlercodes dieser Schnittstellen vom jeweiligen Betriebssystem abhängen, ist der Zugriff auf eine nicht-existierende Datei ein Fehler, der betriebssystemübergreifend ist. Der Fehlercode, der von der Betriebssystemschnittstelle zurückgegeben wird, wird in boost::system::error_code gespeichert. Der Fehlercode, der den fehlgeschlagenen Zugriff auf die nicht-existierende Datei beschreibt, wird in boost::system::error_condition gespeichert.

Die letzte Klasse, die Boost.System anbietet und hier vorgestellt werden soll, ist boost::system::system_error. Sie ist von der Klasse std::runtime_error abgeleitet und ist ein Ausnahmetyp. Sie kann verwendet werden, wenn ein Fehler vom Typ boost::system::error_code in einer Ausnahme transportiert werden soll.

Beispiel 55.5. boost::system::system_error in Aktion
#include <boost/system/error_code.hpp>
#include <boost/system/system_error.hpp>
#include <iostream>

using namespace boost::system;

void fail()
{
  throw system_error{errc::make_error_code(errc::not_supported)};
}

int main()
{
  try
  {
    fail();
  }
  catch (system_error &e)
  {
    error_code ec = e.code();
    std::cerr << ec.value() << '\n';
    std::cerr << ec.category().name() << '\n';
  }
}

Die Funktion fail() wurde dahingehend geändert, dass im Fehlerfall eine Ausnahme vom Typ boost::system::system_error geworfen wird. Wie Sie am Beispiel 55.5 sehen, kann in dieser Ausnahme ein Fehler vom Typ boost::system::error_code transportiert werden. Die Ausnahme wird in main() abgefangen, um wie zuvor den Fehlercode und die Fehlerkategorie auf die Standardausgabe auszugeben. Die bereits erwähnte Funktion boost::asio::ip::host_name() gibt es in einer Variante, die genau so funktioniert.