Die Boost C++ Bibliotheken

Kapitel 3. Boost.ScopeExit

Die Bibliothek Boost.ScopeExit macht es möglich, RAII ohne ressourcenspezifische Klassen zu verwenden.

Beispiel 3.1. BOOST_SCOPE_EXIT in Aktion
#include <boost/scope_exit.hpp>
#include <iostream>

int *foo()
{
  int *i = new int{10};
  BOOST_SCOPE_EXIT(&i)
  {
    delete i;
    i = 0;
  } BOOST_SCOPE_EXIT_END
  std::cout << *i << '\n';
  return i;
}

int main()
{
  int *j = foo();
  std::cout << j << '\n';
}

Boost.ScopeExit bietet mit BOOST_SCOPE_EXIT ein Makro an, mit dem eine Art lokale Funktion definiert werden kann. Die Funktion ist ohne Namen. Sie besteht aus einer Parameterliste, die in runden Klammen angegeben wird, und einem Block, der in geschweiften Klammern steht.

Sie müssen die Headerdatei boost/scoped_exit.hpp einbinden, um das Makro BOOST_SCOPE_EXIT verwenden zu können.

Über die Parameterliste geben Sie Variablen an, auf die Sie im Block zugreifen möchten. Wie bei echten Funktionen findet die Parameterübergabe per Kopie statt. Möchten Sie eine Variable als Referenz übergeben, setzen Sie wie im Beispiel 3.1 ein Ampersand vor den Variablennamen.

Im Block können Sie ausschließlich auf Variablen zugreifen, die in der Parameterliste angegeben sind.

Sinn und Zweck von BOOST_SCOPE_EXIT ist es, Code-Blöcke zu definieren, die ausgeführt werden, wenn der Gültigkeitsbereich, in dem sie definiert sind, endet. So wird im Beispiel 3.1 der Block hinter BOOST_SCOPE_EXIT ausgeführt, wenn die Funktion foo() zurückkehrt.

BOOST_SCOPE_EXIT bietet sich für RAII an, ohne ressourcenspezifische Klassen verwenden zu müssen. foo() greift auf new zu, um dynamisch ein int zu reservieren. Um den Speicher freizugeben, wird mit BOOST_SCOPE_EXIT ein entsprechender Block definiert, in dem delete zur Anwendung kommt. Der Block wird garantiert ausgeführt – auch dann, wenn die Funktion zum Beispiel vorzeitig aufgrund einer Ausnahme beendet wird. BOOST_SCOPE_EXIT steht in diesem Beispiel einem Smartpointer in nichts nach.

Beachten Sie, dass die Variable i im Block hinter BOOST_SCOPE_EXIT auf 0 gesetzt wird. i wird anschließend von foo() zurückgegeben und innerhalb von main() auf die Standardausgabe ausgegeben.

Wenn Sie das Beispiel ausführen, sehen Sie, dass nicht 0 ausgegeben wird. j ist auf einen zufälligen Wert gesetzt – nämlich die Adresse, an der die int-Variable reserviert war. Der Block hinter BOOST_SCOPE_EXIT hat i als Referenz erhalten und den reservierten Speicher freigegeben. Da der Block am Ende von foo() ausgeführt wird, kommt der Schreibzugriff auf i zu spät. Der Rückgabewert von foo() wurde bereits als Kopie von i erstellt, wenn i auf 0 gesetzt wird.

Sie können Boost.ScopeExit ignorieren, wenn Sie in einer Entwicklungsumgebung arbeiten, die C++11 unterstützt. Sie können dann RAII ohne ressourcenspezifische Klassen mit Hilfe von Lambda-Funktionen einsetzen.

Beispiel 3.2. Boost.ScopeExit mit C++11-Lambda-Funktionen
#include <iostream>
#include <utility>

template <typename T>
struct scope_exit
{
  scope_exit(T &&t) : t_{std::move(t)} {}
  ~scope_exit() { t_(); }
  T t_;
};

template <typename T>
scope_exit<T> make_scope_exit(T &&t) { return scope_exit<T>{
  std::move(t)}; }

int *foo()
{
  int *i = new int{10};
  auto cleanup = make_scope_exit([&i]() mutable { delete i; i = 0; });
  std::cout << *i << '\n';
  return i;
}

int main()
{
  int *j = foo();
  std::cout << j << '\n';
}

Beispiel 3.2 definiert eine Klasse scope_exit, der im Konstruktor eine Lambda-Funktion übergeben werden kann. Die Lambda-Funktion wird im Destruktor aufgerufen und ausgeführt. Außerdem ist eine Hilfsfunktion make_scope_exit() definiert, die es ermöglicht, scope_exit zu instanziieren, ohne explizit einen Template-Parameter angeben zu müssen.

In der Funktion foo() wird auf make_scope_exit() zugegriffen und eine Lambda-Funktion als Parameter übergeben. Die Lambda-Funktion sieht genauso aus wie der Block, der im Beispiel 3.1 hinter BOOST_SCOPE_EXIT angegeben wurde: Der Speicherbereich, auf den i zeigt, wird mit delete freigegeben. Anschließend wird i auf 0 gesetzt.

Wenn Sie das Beispiel ausführen, geschieht das Gleiche wie zuvor. So wird nicht nur die dynamisch reservierte int-Variable freigegeben. j ist außerdem nicht auf 0 gesetzt, wenn die Variable ausgegeben wird.

Beispiel 3.3. Besonderheiten rund um BOOST_SCOPE_EXIT
#include <boost/scope_exit.hpp>
#include <iostream>

struct x
{
  int i;

  void foo()
  {
    i = 10;
    BOOST_SCOPE_EXIT(void)
    {
      std::cout << "last\n";
    } BOOST_SCOPE_EXIT_END
    BOOST_SCOPE_EXIT(this_)
    {
      this_->i = 20;
      std::cout << "first\n";
    } BOOST_SCOPE_EXIT_END
  }
};

int main()
{
  x obj;
  obj.foo();
  std::cout << obj.i << '\n';
}

Beispiel 3.3 stellt einige Besonderheiten von BOOST_SCOPE_EXIT vor:

Beispiel 3.3 gibt first, last und 20 in dieser Reihenfolge aus.

Aufgabe

Ersetzen Sie in diesem Programm std::unique_ptr und den benutzerdefinierten Deleter durch BOOST_SCOPE_EXIT:

#include <string>
#include <memory>
#include <cstdio>

struct CloseFile
{
    void operator()(std::FILE *file)
    {
        std::fclose(file);
    }
};

void write_to_file(const std::string &s)
{
    std::unique_ptr<std::FILE, CloseFile> file{
      std::fopen("hello-world.txt", "a") };
    std::fprintf(file.get(), s.c_str());
}

int main()
{
    write_to_file("Hello, ");
    write_to_file("world!");
}