Die Bibliothek Boost.ScopeExit macht es möglich, RAII ohne ressourcenspezifische Klassen zu verwenden.
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.
#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.
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:
Werden mehrere Blöcke im gleichen Gültigkeitsbereich mit BOOST_SCOPE_EXIT
definiert, werden sie von hinten nach vorne ausgeführt. Beispiel 3.3 gibt zuerst first
und dann last
aus.
Sollen keine Variablen einem Block hinter BOOST_SCOPE_EXIT
übergeben werden, muss void
in den runden Klammern angegeben werden. Die runden Klammern dürfen nicht leer sein.
Wird BOOST_SCOPE_EXIT
in einer Methode verwendet, kann über this_ ein Zeiger auf das aktuelle Objekt übergeben werden. Es muss this_ verwendet werden, nicht this
.
Beispiel 3.3 gibt first
, last
und 20
in dieser Reihenfolge aus.
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!");
}