19 мая 2007

Loki:ScopeGuard

Александреску - умный парень, но наверно скорее теоретик, чем практик. Иначе как объяснить такое:

В Loki можно найти полезную вещь - scope guard. Поясню зачем она нужна. Все знакомы с RAII и с тем, что очень удобно делать работу по управлению ресурсами в деструкторе - это безопасно с точки зрения исключений, так как при откате стека по исключению вызываются деструкторы всех уничтожаемых объектов. Например:
class File
{
public:
File(const char* FileName) { OpenFile(FileName); }
~File() { CloseFile(); }
};

{
File f("test.txt");
Foo();
}
Файл закроется даже в случае если Foo бросит исключение.

Однако создавать такие RAII-классы для каждого случая - довольно муторная работа. Представьте:
{
class Guard
{
public:
Guard(Module& M, Window& W) : m(M), w(W) {}
~Guard() { w.Close(); m.Terminate(); }
private:
Window& w;
Module& m;
}

Guard g(module, window);
Foo();
}
Гораздо проще было бы жить, если бы можно было записать все это намного короче:
{
AutoGuard(window.Close());
AutoGuard(module.Terminate());
Foo();
}
Так вот предложенная Александреску конструкция такое счастье и привносит в нашу жизнь, выглядит это так:
{
LOKI_ON_BLOCK_EXIT_OBJ(window, Windows::Close);
LOKI_ON_BLOCK_EXIT_OBJ(module, Module::Terminate);
Foo();
Все бы замечательно, но это не компилируется без using namespace Loki, так как макрос LOKI_ON_BLOCK_EXIT_OBJ выглядит так:
#define LOKI_ON_BLOCK_EXIT_OBJ ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = MakeObjGuard
А ведь дядька Александреску мог бы и пожалеть людей и написать так:
#define LOKI_ON_BLOCK_EXIT_OBJ ::Loki::ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = ::Loki::MakeObjGuard

А ведь не написал. Видимо не использовал это в реальных проектах. Или у него там сплошные using namespace xxx стоят?

PS. Оказывается уже исправили.

2 комментария:

AnToXa комментирует...

имхо не очень удобно это все как раз, например если надо уметь сделать release().
или записать какую-то простейшую логику в dtor.

если уж надо сделать что-то вроде scope guard, то я обычно пишу прям в теле функции:

struct guard {
pointer_type ptr;
~guard() { if (ptr) { destroy_ptr(ptr); } }
} g_ = { some_ptr } ;

плюс именно в том, что можно писать все что угодно в этом деструкторе, а не только вызовы функций и если надо сделать release() - просто пишем g_.ptr = 0; и всех дел.

Raider комментирует...

Да, release() иногда полезен. А scope guard из loki - он не на все случаи жизни, а для определенных ситуаций - и с ними он справляется одной строкой, в этом и прелесть.