Scope Guard средствами C++0x: часть 1
Часть первая: решение в лоб
Scope Guard — одно из средств автоматического освобождения ресурсов при выходе за пределы видимости переменной, с ними связанной. Scope Guard предоставляет базовую гарантию безопасности исключений. Авторами этой идеи (по-видимому) являются Andrei Alexandrescu и Petru Marginean. Если вы с этой статьёй еще не знакомы, то очень рекомендую к прочтению.
Реализация Scope Guard довольно простая, но из-за того, что C++ не поддерживал шаблоны с переменным количеством параметров, приходилось создавать несколько шаблонов — в зависимости от того, сколько аргументов принимает функция, выполняющая освобождение ресурсов.
В C++0x добавлена поддержка шаблонов с переменным количеством параметров (variadic templates). Мне было интересно переписать Scope Guard одним шаблоном — хорошее погружение в новые возможности языка.
Получился примерно такой код (он не самый оптимальный в плане количества строк, можно написать проще, но об этом в следующей части):
#define SJ_XGUARD_H_
#include <tuple>
namespace {
template<unsigned int N>
struct Apply_Helper {
template<typename F, typename... ArgsT, typename... Args>
static void apply(const F& f, const std::tuple<ArgsT...>&& t, Args&&... args)
{
Apply_Helper<N-1>::apply(f, t, std::get<N-1>(t), args...);
}
};
template<>
struct Apply_Helper<0> {
template<typename F, typename... ArgsT, typename... Args>
static void apply(const F& f, const std::tuple<ArgsT...>&&, Args&&... args)
{
f(args...);
}
};
template<typename F, typename... ArgsT>
void apply(const F& f, std::tuple<ArgsT...> const&& t)
{
Apply_Helper<sizeof...(ArgsT)>::apply(f, t);
}
};
template<typename Function, typename... Args>
class XGuard {
public:
XGuard(const Function& aFunction, std::tuple<Args...>&& aParams)
: m_fSucceeded(false),
m_Function(aFunction),
m_Params(aParams)
{
}
~XGuard(void) throw()
{
if (!this->m_fSucceeded) {
try {
apply(this->m_Function, this->m_Params);
}
catch(...) {
}
}
}
XGuard(XGuard&& other)
: m_fSucceeded(other.m_fSucceeded),
m_Function(other.m_Function),
m_Params(other.m_Params)
{
other.commit();
}
void commit(void) const throw()
{
this->m_fSucceeded = true;
}
XGuard& operator=(XGuard&& other)
{
if (&other != this) {
this->m_fSucceeded = other.m_fSucceeded;
this->m_Function = other.m_Function;
this->m_Params = std::move(other.m_Params);
other.m_Function = NULL;
other.commit();
}
return *this;
}
XGuard(const XGuard& other) = delete;
XGuard& operator=(const XGuard& other) = delete;
private:
mutable bool m_fSucceeded;
Function m_Function;
std::tuple<Args...>&& m_Params;
};
template<typename F, typename... A>
inline XGuard<F, A...> makeXGuard(const F& f, std::tuple<A...>&& p)
{
return XGuard<F, A...>(f, p);
}
#define MAKEXGUARD(f, ...) \
makeXGuard((f), std::make_tuple(__VA_ARGS__))
#endif /* SJ_XGUARD_H_ */
Название XGuard чисто историческое, я в университете занимался «велосипедостроительством», поэтому просто переделал код многолетней давности.
Основной трудностью для меня было развернуть std::tuple в список параметров. Отвык программировать на Prolog
Развёрткой std::tuple в список параметров занимается Apply_Helper в анонимном пространстве имён. Для программировавших на чем-нибудь типа Prolog принцип очень простой:
Другой подводный камень — макро MAKEXGUARD. Дело в том, что такая реализация:
inline XGuard<F, A...> makeXGuard(const F& f, const A&& p)
{
return XGuard<F, A...>(f, std::make_tuple(p));
}
не воспринимает std::ref()/std::cref() в списках параметров — у компилятора не получается определить корректный шаблон на основе вывода параметров.
Как я уже говорил, приведённый код не является оптимальным в плане количества строк (можно сделать проще), но не генерирует лишнего машинного кода. Вся свёртка/развёртка параметров производится на этапе компиляции.
Например, такой код:
//#include <iostream>
#include <functional>
#include "xguard.h"
static void my_free(void*& p)
{
if (p) {
free(p);
p = NULL;
}
}
int main(int, char**)
{
{
void* p = (void*)std::malloc(100);
// std::cout << p << std::endl;
auto guard = MAKEXGUARD(&my_free, std::ref(p));
// std::cout << p << std::endl;
}
return 0;
}
транслируется в такой:
sub rsp, 24
mov edi, 100
call malloc ; void* p = (void*)std::malloc(100);
lea rdi, [rsp+8]
mov QWORD PTR [rsp+8], rax
call _ZL7my_freeRPv ; my_free(p);
xor eax, eax
add rsp, 24
ret
_ZL7my_freeRPv:
push rbx
mov rbx, rdi
mov rdi, QWORD PTR [rdi]
test rdi, rdi
je .L5
call free
mov QWORD PTR [rbx], 0
.L5:
pop rbx
ret
Таким образом, вызов функции makeXGuard() и конструктора XGuard::XGuard() вообще не генерирует никакого кода! А при выходе p из области видимости автоматически генерируется вызов my_free(p) (то есть то же самое, что и написал бы программист, не использующий Scope Guard). Иными словами, накладных расходов нет. Красота!
Апр
2010
Комментарии к статье «Scope Guard средствами C++0x: часть 1» (2) »
Пожалуйста, не используйте эту форму для комментирования! Данная форма предназначена исключительно для ботов.
Оставить комментарий к записи «Scope Guard средствами C++0x: часть 1»
गते गते पारगते पारसंगते बोधि स्वाहा
Меня зовут Владимир, я программист-фрилансер, специализирующийся на Web-программировании и програмировании под Linux.
По совместительству занимаюсь администрированием LAMP/LNMP-серверов и техническим переводом.


[...] This post was mentioned on Twitter by Интернет заработок. Интернет заработок said: V.Kolesnikov: Scope Guard средствами C++0x [...]
[...] В прошлой части была рассмотрена реализация Scope Guard средствами C++0x. Благодаря шаблонам с переменным количеством параметров (variadic templates), реализация на C++0x получилась несколько проще, чем в оригинале, так как один и тот же шаблонный класс может использоваться для создания Scope Guard с различным количеством параметров. [...]