Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 21.09.03 00:11
Оценка: 199 (20)
Думаю, многие знакомы с ScopeGuard.

Мне очень понравилась его идея, но только он не привел исходников — видимо, по его мнению, любой желающий может сам реализовать эту кучу байндеров.

Последнее время мне много приходится работать с legacy кодом, поэтому проблема стала для меня крайне острой.

Будучи ленивым по натуре, я хотел использовать что-либо готовое. Но готового ScopeGuard я не мог найти.

Решение — паразитировать на boost::bind. Преимущество данного решения в том, что не нужно реализовывать кучу байндеров вручную, причем для разных calling conventions, — все уже давно реализовано в boost::bind.

////////////////////////////////////////////////////////////////////////////////////////////////
// scope_guard.hpp

#pragma once

#include <boost/bind.hpp>

////////////////////////////////////////////////////////////////////////////////////////////////

namespace util
{

////////////////////////////////////////////////////////////////////////////////////////////////

namespace detail
{

////////////////////////////////////////////////////////////////////////////////////////////////

class scope_guard_base
{
public:
    void commit() const
    {
        do_rollback_ = false;
    }

protected:
    scope_guard_base()
        :    do_rollback_(true)
    {}

    mutable bool do_rollback_;
};

////////////////////////////////////////////////////////////////////////////////////////////////

template<class R, class F, class A>
class scope_guard_impl : public scope_guard_base
{
private:
    typedef boost::_bi::bind_t<R, F, A> binder;

public:
    explicit scope_guard_impl(const binder& b)
        :    rollback_(b)
    {}

    ~scope_guard_impl()
    {
        if(do_rollback_)
            rollback_();
    }

private:
    binder rollback_;
};

////////////////////////////////////////////////////////////////////////////////////////////////

} // namespace detail

////////////////////////////////////////////////////////////////////////////////////////////////

typedef const detail::scope_guard_base& scope_guard;

template<class R, class F, class A>
inline detail::scope_guard_impl<R, F, A> make_guard(const boost::_bi::bind_t<R, F, A>& b)
{
    return detail::scope_guard_impl<R, F, A>(b);
}

////////////////////////////////////////////////////////////////////////////////////////////////

} // namespace util

////////////////////////////////////////////////////////////////////////////////////////////////


Пример использования:

////////////////////////////////////////////////////////////////////////////////////////////////
// test.cpp

#include <windows.h>

#include <boost/ref.hpp>

#define BOOST_BIND_ENABLE_STDCALL
#include "scope_guard.hpp"

////////////////////////////////////////////////////////////////////////////////////////////////

using util::scope_guard;
using util::make_guard;

////////////////////////////////////////////////////////////////////////////////////////////////

void test_1()
{
    void* p = 0;

    // передадим ссылку на p, отложим вызов free(p)
    scope_guard g(make_guard(boost::bind(&free, boost::ref(p))));

    p = malloc(0x10);

    // здесь произойдет ~g === free(p)
}

////////////////////////////////////////////////////////////////////////////////////////////////

void test_2()
{
    void *p = 0;

    p = malloc(0x10); // (1)
    // передадим текущее значение p, отложим вызов free(p)
    scope_guard g1(make_guard(boost::bind(&free, p)));

    p = malloc(0x10); // (2)
    // передадим текущее значение p, отложим вызов free(p)
    scope_guard g2(make_guard(boost::bind(&free, p)));

    p = malloc(0x10); // (3)
    // передадим текущее значение p, отложим вызов free(p)
    scope_guard g3(make_guard(boost::bind(&free, p))); 

    // здесь произойдет ~g3 === free(p), p == (3)
    // здесь произойдет ~g2 === free(p), p == (2)
    // здесь произойдет ~g1 === free(p), p == (1)
}

////////////////////////////////////////////////////////////////////////////////////////////////

void test_3()
{
    ::CoInitialize(0);

    // отложим вызов ::CoUninitialize()
    scope_guard g1(make_guard(boost::bind(&::CoUninitialize))); 

    // здесь произойдет ~g1 === ::CoUninitialize()
}

////////////////////////////////////////////////////////////////////////////////////////////////

void test_4()
{
    void* p = ::HeapAlloc(::GetProcessHeap(), 0, 0x10);
    
    // отложим вызов ::HeapFree(::GetProcessHeap(), 0, p)
    scope_guard g1(make_guard(boost::bind(&::HeapFree, ::GetProcessHeap(), 0, p))); // (1)

    // здесь произойдет ~g1 === ::HeapFree(::GetProcessHeap(), p), ::GetProcessHeap() == (1), p == (1)
}

////////////////////////////////////////////////////////////////////////////////////////////////


int main()
{
    test_1();
    test_2();
    test_3();
    test_4();

    return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////
Re: Ленивый ScopeGuard (не путать с голубцами!)
От: alexkro  
Дата: 21.09.03 07:48
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Думаю, многие знакомы с ScopeGuard.


ME>Мне очень понравилась его идея, но только он не привел исходников — видимо, по его мнению, любой желающий может сам реализовать эту кучу байндеров.


Почему не привел? В этом архиве они (ftp://ftp.cuj.com/pub/2000/cujdec2000.zip).
Re[2]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 21.09.03 09:34
Оценка:
Здравствуйте, alexkro, Вы писали:

A>Здравствуйте, MaximE, Вы писали:


ME>>Думаю, многие знакомы с ScopeGuard.


ME>>Мне очень понравилась его идея, но только он не привел исходников — видимо, по его мнению, любой желающий может сам реализовать эту кучу байндеров.


A>Почему не привел? В этом архиве они (ftp://ftp.cuj.com/pub/2000/cujdec2000.zip).


Посыпаю голову пеплом — не мог найти

Но все равно с boost::bind как-то милее сердцу
Re: Ленивый ScopeGuard (не путать с голубцами!)
От: Аноним  
Дата: 21.09.03 15:11
Оценка: 53 (4)
Здравствуйте, MaximE, Вы писали:

<...>

Вах ! Однако меня несколько коробит повторяющаяся последовательность "make_guard(boost::bind(" и почти всегда совершенно ненужные переменные g1,g2. А вот как бы добавить макросов, чтобы можно было писать

SCOPE_GUARD(&free,p);
SCOPE_GUARD(&::HeapFree,::GetProcessHeap(),0,p);

?

Интересная задача. Если только не останавливаться на варианте с двойными скобками
SCOPE_GUARD((&free,p));
SCOPE_GUARD((&::HeapFree,::GetProcessHeap(),0,p));

, надо как-то модифицировать выражение scope_guard g2(make_guard(boost::bind(&free, p))) так, чтобы в конце была одна скобка.
Тогда
#define SCOPE_GUARD \
        scope_guard guardAt##__LINE__=make_guard_ex

И вуаля !

Однако боюсь, что иного варианта кроме написания make_guard_ex для количества аргументов [1...N] нет... Зато пользователи (и кода и гуарда) будут довольны !
Re[2]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 21.09.03 16:01
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, MaximE, Вы писали:


<...>>

А>Вах ! Однако меня несколько коробит повторяющаяся последовательность "make_guard(boost::bind(" ...


Да, меня это тоже несколько подплющивает


А>... и почти всегда совершенно ненужные переменные g1,g2. А вот как бы добавить макросов, чтобы можно было писать


А вот про ненужные переменные я даже не задумывался.

[]

А>Однако боюсь, что иного варианта кроме написания make_guard_ex для количества аргументов [1...N] нет... Зато пользователи (и кода и гуарда) будут довольны !


Отличная идея.
Re[3]: Ленивый ScopeGuard (не путать с голубцами!)
От: Дмитрий Наумов  
Дата: 21.09.03 17:48
Оценка: 14 (1)
Здравствуйте, MaximE, Вы писали:

А>>Вах ! Однако меня несколько коробит повторяющаяся последовательность "make_guard(boost::bind(" ...


ME>Да, меня это тоже несколько подплющивает


Господа, о чем это вы?! А как же приведенный в той статье и в исходника макрос ON_EXIT???


А>>... и почти всегда совершенно ненужные переменные g1,g2. А вот как бы добавить макросов, чтобы можно было писать


ME>А вот про ненужные переменные я даже не задумывался.


В вышеупомянутом макросе и решается проблема создания "безымянных" переменных...
Re[4]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 21.09.03 18:04
Оценка:
Здравствуйте, Дмитрий Наумов, Вы писали:

ДН>Здравствуйте, MaximE, Вы писали:


А>>>Вах ! Однако меня несколько коробит повторяющаяся последовательность "make_guard(boost::bind(" ...


ME>>Да, меня это тоже несколько подплющивает


ДН>Господа, о чем это вы?! А как же приведенный в той статье и в исходника макрос ON_EXIT???


А>>>... и почти всегда совершенно ненужные переменные g1,g2. А вот как бы добавить макросов, чтобы можно было писать


ME>>А вот про ненужные переменные я даже не задумывался.


ДН>В вышеупомянутом макросе и решается проблема создания "безымянных" переменных...


Да, точно.

#define CONCATENATE_DIRECT(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_DIRECT(s1, s2)
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)

#define ON_BLOCK_EXIT ScopeGuard ANONYMOUS_VARIABLE(scopeGuard) = MakeGuard
#define ON_BLOCK_EXIT_OBJ ScopeGuard ANONYMOUS_VARIABLE(scopeGuard) = MakeObjGuard
Re[4]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 21.09.03 19:40
Оценка:
Здравствуйте, Дмитрий Наумов, Вы писали:

ДН>Здравствуйте, MaximE, Вы писали:


А>>>Вах ! Однако меня несколько коробит повторяющаяся последовательность "make_guard(boost::bind(" ...


ME>>Да, меня это тоже несколько подплющивает


ДН>Господа, о чем это вы?! А как же приведенный в той статье и в исходника макрос ON_EXIT???


Здесь нам придется завернуть два вызова функций, поэтому если далать макрос как в оригинальной статье, то его использование будеть выглядеть примерно так:

ON_BLOCK_EXIT(::CoUninitialize)); // <--- закрыть две скобки


Не очень элегантно, да?
Re[5]: Ленивый ScopeGuard (не путать с голубцами!)
От: Дмитрий Наумов  
Дата: 22.09.03 07:38
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>
ME>ON_BLOCK_EXIT(::CoUninitialize)); // <--- закрыть две скобки
ME>


ME>Не очень элегантно, да?


Насчет двух закрывающих скобок совсем не понял... Если не трудно, можно поподробней?
Re[6]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 22.09.03 09:01
Оценка:
Здравствуйте, Дмитрий Наумов, Вы писали:

ДН>Здравствуйте, MaximE, Вы писали:


ME>>
ME>>ON_BLOCK_EXIT(::CoUninitialize)); // <--- закрыть две скобки
ME>>


ME>>Не очень элегантно, да?


ДН>Насчет двух закрывающих скобок совсем не понял... Если не трудно, можно поподробней?


Если мы напишем подобный макрос:

#define ON_BLOCK_EXIT \
    util::scope_guard some_anonymous_variable_macro (util::make_guard(boost::bind


пользовать его придется так:

void test_1()
{
    void* p = malloc(0x10);
    ON_BLOCK_EXIT(&free, p)));
}
Re[7]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 22.09.03 09:05
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Если мы напишем подобный макрос:


Вот такой макрос (для VC2003):

////////////////////////////////////////////////////////////////////////////////////////////////

#include <boost/preprocessor/cat.hpp>

////////////////////////////////////////////////////////////////////////////////////////////////

#define ON_BLOCK_EXIT \
    util::scope_guard BOOST_PP_CAT(scope_guard_,__COUNTER__) \
        (util::make_guard(boost::bind

////////////////////////////////////////////////////////////////////////////////////////////////
Re[8]: Ленивый ScopeGuard (не путать с голубцами!)
От: Дмитрий Наумов  
Дата: 22.09.03 09:15
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Здравствуйте, MaximE, Вы писали:


ME>>Если мы напишем подобный макрос:


Я чего то не понимаю... Зачем писать свой? Почему не использовать предложенный авторами?
Re[8]: Ленивый ScopeGuard (не путать с голубцами!)
От: Дмитрий Наумов  
Дата: 22.09.03 09:22
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Здравствуйте, MaximE, Вы писали:


ME>>Если мы напишем подобный макрос:


Ну или даже если нам очень захотелось по своему переписать:
#define ON_BLOCK_EXIT \
    util::scope_guard BOOST_PP_CAT(scope_guard_,__COUNTER__) \
        (util::make_guard(boost::bind


то есть маленькое несоответсвие с идеей автора —
#define ON_BLOCK_EXIT ScopeGuard ANONYMOUS_VARIABLE(scopeGuard) = MakeGuard


и тогда, насколько я понимаю открывающая скобка перед util:make_guard не нужна... Возможно, правда, это все связано с BOOST_PP_CAT — я, к сожалению, не сильный знаток буста, так что звиняйте если не прав.
Re[9]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 22.09.03 09:29
Оценка:
Здравствуйте, Дмитрий Наумов, Вы писали:

ДН>Здравствуйте, MaximE, Вы писали:


ME>>Здравствуйте, MaximE, Вы писали:


ME>>>Если мы напишем подобный макрос:


ДН>Ну или даже если нам очень захотелось по своему переписать:

ДН>
ДН>#define ON_BLOCK_EXIT \
ДН>    util::scope_guard BOOST_PP_CAT(scope_guard_,__COUNTER__) \
ДН>        (util::make_guard(boost::bind
ДН>


ДН>то есть маленькое несоответсвие с идеей автора —

ДН>
ДН>#define ON_BLOCK_EXIT ScopeGuard ANONYMOUS_VARIABLE(scopeGuard) = MakeGuard
ДН>


ДН>и тогда, насколько я понимаю открывающая скобка перед util:make_guard не нужна...


Да, можно избавиться от одной пары скобок, применив copy initialization (заменить scoped_guard s(make_guard(...)), на scoped_guard s = make_guard(...)), но это пробемы не решает, т.к. у нас два вызова функций ( make_guard(bind(...)) ) и, соответственно, нам нужно закрыть две скобки.
Re[10]: Ленивый ScopeGuard (не путать с голубцами!)
От: Дмитрий Наумов  
Дата: 22.09.03 10:01
Оценка: 9 (1)
Здравствуйте, MaximE, Вы писали:

ME>Да, можно избавиться от одной пары скобок, применив copy initialization (заменить scoped_guard s(make_guard(...)), на scoped_guard s = make_guard(...)), но это пробемы не решает, т.к. у нас два вызова функций ( make_guard(bind(...)) ) и, соответственно, нам нужно закрыть две скобки.


Но и открыть две, разве не так?
ON_BLOCK_EXIT( bind(...) );


Имхо, выглядит очень даже обычно и прилично...
Re[11]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 22.09.03 10:06
Оценка:
Здравствуйте, Дмитрий Наумов, Вы писали:

ДН>Здравствуйте, MaximE, Вы писали:


ME>>Да, можно избавиться от одной пары скобок, применив copy initialization (заменить scoped_guard s(make_guard(...)), на scoped_guard s = make_guard(...)), но это пробемы не решает, т.к. у нас два вызова функций ( make_guard(bind(...)) ) и, соответственно, нам нужно закрыть две скобки.


ДН>Но и открыть две, разве не так?

ДН>
ДН>ON_BLOCK_EXIT( bind(...) );
ДН>


ДН>Имхо, выглядит очень даже обычно и прилично...


Если не пытаться загнать bind внутрь макроса, то да
Re[12]: Ленивый ScopeGuard (не путать с голубцами!)
От: Дмитрий Наумов  
Дата: 22.09.03 10:11
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Если не пытаться загнать bind внутрь макроса, то да


К такой ошеломляющей "наглости" я не был готов... Bind...внутри макроса...Победа нокаутом

А если не секрет — неужели так надо его именно внутрь запихнуть?
Re[13]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 22.09.03 10:42
Оценка:
Здравствуйте, Дмитрий Наумов, Вы писали:

ДН>Здравствуйте, MaximE, Вы писали:


ME>>Если не пытаться загнать bind внутрь макроса, то да


ДН>К такой ошеломляющей "наглости" я не был готов... Bind...внутри макроса...Победа нокаутом

ДН>
ДН>А если не секрет — неужели так надо его именно внутрь запихнуть?

Не знаю
Меня без макросов вполне устраивает.
Re[14]: Ленивый ScopeGuard (не путать с голубцами!)
От: Дмитрий Наумов  
Дата: 22.09.03 10:53
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Не знаю

ME>Меня без макросов вполне устраивает.

Позвольте угадаю сей до боли знакомый (по себе самому) эффект — так понравилась статья, что до конца не дочитал — бросился использовать! А оказалось, что там какие то еще макросы приведены, да и то, только в приложенном коде... Так?
Re[15]: Ленивый ScopeGuard (не путать с голубцами!)
От: MaximE Великобритания  
Дата: 22.09.03 11:03
Оценка: :)
Здравствуйте, Дмитрий Наумов, Вы писали:

ДН>Здравствуйте, MaximE, Вы писали:


ME>>Не знаю

ME>>Меня без макросов вполне устраивает.

ДН>Позвольте угадаю сей до боли знакомый (по себе самому) эффект — так понравилась статья, что до конца не дочитал — бросился использовать! А оказалось, что там какие то еще макросы приведены, да и то, только в приложенном коде... Так?


Оригинальную статью я читал давно, и помнил, что там упоминались макросы, но до последнего времени я не использовал scope_guard вообще — не было необходимости. Понадобилось — я сразу подумал про boost::bind — всего делов-то засунуть вызов в деструктор.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.