Tiny RAII для тестирования фрагментов [дубликат]

Очищено пример @Deleet

from collections import Iterable

def flatten(l, a=[]):
    for i in l:
        if isinstance(i, Iterable):
            flatten(i, a)
        else:
            a.append(i)
    return a

daList = [[1,4],[5,6],[23,22,234,2],[2], [ [[1,2],[1,2]],[[11,2],[11,22]] ] ]

print(flatten(daList))

Пример: https://repl.it/G8mb/0

27
задан lurscher 23 April 2012 в 18:18
поделиться

11 ответов

Boost.ScopeExit - это макрос, который должен работать с кодом не C ++ 11, то есть кодом, который не имеет доступа к lambdas на языке. Он использует некоторые умные шаблонные хаки (например, злоупотребляет двусмысленностью, возникающей из-за использования < как для шаблонов, так и для операторов сравнения!) И препроцессора для эмуляции лямбда-функций. Вот почему код длиннее.

Показанный код также является ошибкой (что, вероятно, является самой сильной причиной для использования существующего решения): он вызывает неопределенное поведение из-за возврата ссылок на временные файлы.

Поскольку вы пытаетесь использовать возможности C ++ 11, код можно было бы значительно улучшить, используя семантику перемещения, ссылки на rvalue и совершенную пересылку:

template< typename Lambda >
class ScopeGuard
{
    bool committed; // not mutable
    Lambda rollbackLambda; 
    public:


        // make sure this is not a copy ctor
        template <typename L,
                  DisableIf<std::is_same<RemoveReference<RemoveCv<L>>, ScopeGuard<Lambda>>> =_
        >
        /* see http://loungecpp.net/w/EnableIf_in_C%2B%2B11
         * and http://stackoverflow.com/q/10180552/46642 for info on DisableIf
         */
        explicit ScopeGuard(L&& _l)
        // explicit, unless you want implicit conversions from *everything*
        : committed(false)
        , rollbackLambda(std::forward<L>(_l)) // avoid copying unless necessary
        {}

        template< typename AdquireLambda, typename L >
        ScopeGuard( AdquireLambda&& _al , L&& _l) : committed(false) , rollbackLambda(std::forward<L>(_l))
        {
            std::forward<AdquireLambda>(_al)(); // just in case the functor has &&-qualified operator()
        }

        // move constructor
        ScopeGuard(ScopeGuard&& that)
        : committed(that.committed)
        , rollbackLambda(std::move(that.rollbackLambda)) {
            that.committed = true;
        }

        ~ScopeGuard()
        {
            if (!committed)
                rollbackLambda(); // what if this throws?
        }
        void commit() { committed = true; } // no need for const
};

template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuard( aLambda&& _a , rLambda&& _r) // again perfect forwarding
{
    return ScopeGuard< rLambda >( std::forward<aLambda>(_a) , std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
}

template<typename rLambda>
ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r)
{
    return ScopeGuard< rLambda >( std::forward<rLambda>(_r ));
}
20
ответ дан R. Martinho Fernandes 24 August 2018 в 10:39
поделиться

Вам может быть интересно увидеть это презентацию самим Андреем самостоятельно, как улучшить scopedguard с помощью c ++ 11

14
ответ дан André 24 August 2018 в 10:39
поделиться

Вы уже выбрали ответ, но я все равно возьму вызов:

#include <iostream>
#include <type_traits>
#include <utility>

template < typename RollbackLambda >
class ScopeGuard;

template < typename RollbackLambda >
auto  make_ScopeGuard( RollbackLambda &&r ) -> ScopeGuard<typename
 std::decay<RollbackLambda>::type>;

template < typename RollbackLambda >
class ScopeGuard
{
    // The input may have any of: cv-qualifiers, l-value reference, or both;
    // so I don't do an exact template match.  I want the return to be just
    // "ScopeGuard," but I can't figure it out right now, so I'll make every
    // version a friend.
    template < typename AnyRollbackLambda >
    friend
    auto make_ScopeGuard( AnyRollbackLambda && ) -> ScopeGuard<typename
     std::decay<AnyRollbackLambda>::type>;

public:
    using lambda_type = RollbackLambda;

private:
    // Keep the lambda, of course, and if you really need it at the end
    bool        committed;
    lambda_type  rollback;

    // Keep the main constructor private so regular creation goes through the
    // external function.
    explicit  ScopeGuard( lambda_type rollback_action )
        : committed{ false }, rollback{ std::move(rollback_action) }
    {}

public:
    // Do allow moves
    ScopeGuard( ScopeGuard &&that )
        : committed{ that.committed }, rollback{ std::move(that.rollback) }
    { that.committed = true; }
    ScopeGuard( ScopeGuard const & ) = delete;

    // Cancel the roll-back from being called.
    void  commit()  { committed = true; }

    // The magic happens in the destructor.
    // (Too bad that there's still no way, AFAIK, to reliably check if you're
    // already in exception-caused stack unwinding.  For now, we just hope the
    // roll-back doesn't throw.)
    ~ScopeGuard()  { if (not committed) rollback(); }
};

template < typename RollbackLambda >
auto  make_ScopeGuard( RollbackLambda &&r ) -> ScopeGuard<typename
 std::decay<RollbackLambda>::type>
{
    using std::forward;

    return ScopeGuard<typename std::decay<RollbackLambda>::type>{
     forward<RollbackLambda>(r) };
}

template < typename ActionLambda, typename RollbackLambda >
auto  make_ScopeGuard( ActionLambda && a, RollbackLambda &&r, bool
 roll_back_if_action_throws ) -> ScopeGuard<typename
 std::decay<RollbackLambda>::type>
{
    using std::forward;

    if ( not roll_back_if_action_throws )  forward<ActionLambda>(a)();
    auto  result = make_ScopeGuard( forward<RollbackLambda>(r) );
    if ( roll_back_if_action_throws )  forward<ActionLambda>(a)();
    return result;
}

int  main()
{
    auto aa = make_ScopeGuard( []{std::cout << "Woah" << '\n';} );
    int  b = 1;

    try {
     auto bb = make_ScopeGuard( [&]{b *= 2; throw b;}, [&]{b = 0;}, true );
    } catch (...) {}
    std::cout << b++ << '\n';
    try {
     auto bb = make_ScopeGuard( [&]{b *= 2; throw b;}, [&]{b = 0;}, false );
    } catch (...) {}
    std::cout << b++ << '\n';

    return 0;
}
// Should write: "0", "2", and "Woah" in that order on separate lines.

Вместо того, чтобы создавать функции создания и конструктор, вы ограничиваетесь только функциями создания, причем основным конструктором является private. Я не мог понять, как ограничить экземпляры friend -ed только теми, которые связаны с текущим параметром шаблона. (Возможно, потому, что параметр упоминается только в возвращаемом типе.) Возможно, на этом сайте может быть исправлено исправление. Поскольку первое действие не нужно хранить, оно присутствует только в функциях создания. Существует флаг Boolean, если флаг throw от первого действия вызывает возврат или нет.

Часть std::decay разделяет как cv-квалификаторы, так и ссылочные маркеры. Но вы не можете использовать его для этой общей цели, если тип ввода является встроенным массивом, так как он будет применять преобразование между массивами и указателями.

0
ответ дан CTMacUser 24 August 2018 в 10:39
поделиться

Без отслеживания обязательств, но очень аккуратно и быстро.

template <typename F>
struct ScopeExit {
    ScopeExit(F&& f) : m_f(std::forward<F>(f)) {}
    ~ScopeExit() { m_f(); }
    F m_f;
};

template <typename F>
ScopeExit<F> makeScopeExit(F&& f) {
    return ScopeExit<F>(std::forward<F>(f));
};

#define STRING_JOIN(arg1, arg2) STRING_JOIN2(arg1, arg2)
#define STRING_JOIN2(arg1, arg2) arg1 ## arg2

#define ON_SCOPE_EXIT(code) auto STRING_JOIN(scopeExit, __LINE__) = makeScopeExit([&](){code;})

Использование

{
    puts("a");
    auto _ = makeScopeExit([]() { puts("b"); });
    // More readable with a macro
    ON_SCOPE_EXIT(puts("c"));
} # prints a, c, b
2
ответ дан ens 24 August 2018 в 10:39
поделиться

Вероятность того, что этот подход будет стандартизован в C ++ 17 или в Основах Библиотеки TS посредством предложения P0052R0

template <typename EF>
scope_exit<see below> make_scope_exit(EF &&exit_function) noexcept;

template <typename EF>
scope_exit<see below> make_scope_fail(EF && exit_function) noexcept;

template <typename EF>
scope_exit<see below> make_scope_success(EF && exit_function) noexcept;

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

6
ответ дан Erik van Velzen 24 August 2018 в 10:39
поделиться

Еще короче: я не знаю, почему вы, ребята, настаиваете на том, чтобы поместить шаблон в класс охраны.

#include <functional>

class scope_guard {
public: 
    template<class Callable> 
    scope_guard(Callable && undo_func) try : f(std::forward<Callable>(undo_func)) {
    } catch(...) {
        undo_func();
        throw;
    }

    scope_guard(scope_guard && other) : f(std::move(other.f)) {
        other.f = nullptr;
    }

    ~scope_guard() {
        if(f) f(); // must not throw
    }

    void dismiss() noexcept {
        f = nullptr;
    }

    scope_guard(const scope_guard&) = delete;
    void operator = (const scope_guard&) = delete;

private:
    std::function<void()> f;
};

Обратите внимание, что код очистки не бросается, иначе вы получаете в том же случае, что и при метании деструкторов.

Использование:

// do step 1
step1();
scope_guard guard1 = [&]() {
    // revert step 1
    revert1();
};

// step 2
step2();
guard1.dismiss();

Мое вдохновение было тем же статьей DrDobbs , что и для OP.


Редактировать 2017/2018: После просмотра (некоторые из) презентации Андрея , с которой связан Андре (я пропустил до конца, где он сказал «Больно близко к идеалу!»). Я понял, что это выполнимо. Большую часть времени вы не хотите иметь дополнительных охранников для всего. Вы просто делаете что-то, и, в конце концов, это либо преуспевает, либо должен произойти откат.

Изменить 2018: добавлена ​​политика выполнения, которая устраняет необходимость вызова dismiss.

#include <functional>
#include <deque>

class scope_guard {
public:
    enum execution { always, no_exception, exception };

    scope_guard(scope_guard &&) = default;
    explicit scope_guard(execution policy = always) : policy(policy) {}

    template<class Callable>
    scope_guard(Callable && func, execution policy = always) : policy(policy) {
        this->operator += <Callable>(std::forward<Callable>(func));
    }

    template<class Callable>
    scope_guard& operator += (Callable && func) try {
        handlers.emplace_front(std::forward<Callable>(func));
        return *this;
    } catch(...) {
        if(policy != no_exception) func();
        throw;
    }

    ~scope_guard() {
        if(policy == always || (std::uncaught_exception() == (policy == exception))) {
            for(auto &f : handlers) try {
                f(); // must not throw
            } catch(...) { /* std::terminate(); ? */ }
        }
    }

    void dismiss() noexcept {
        handlers.clear();
    }

private:
    scope_guard(const scope_guard&) = delete;
    void operator = (const scope_guard&) = delete;

    std::deque<std::function<void()>> handlers;
    execution policy = always;
};

Использование:

scope_guard scope_exit, scope_fail(scope_guard::execution::exception);

action1();
scope_exit += [](){ cleanup1(); };
scope_fail += [](){ rollback1(); };

action2();
scope_exit += [](){ cleanup2(); };
scope_fail += [](){ rollback2(); };

// ...
24
ответ дан Fozi 24 August 2018 в 10:39
поделиться

makeScopeGuard возвращает ссылку на константу. Вы не можете сохранить эту константную ссылку в const ref со стороны вызывающего абонента в строке, подобной:

const auto& a = RAII::makeScopeGuard( [&]() { myVec.pop_back(); } ); 

. Таким образом, вы вызываете неопределенное поведение.

Herb Sutter GOTW 88 дает некоторое представление о сохранении значений в ссылках на const.

3
ответ дан Ken Bloom 24 August 2018 в 10:39
поделиться

Вы можете использовать std::unique_ptr для этой цели, которая реализует шаблон RAII. Например:

vector<int> v{};
v.push_back(42);
unique_ptr<decltype(v), function<void(decltype(v)*)>>
    p{&v, [] (decltype(v)* v) { if (uncaught_exception()) { v->pop_back(); }}};
throw exception(); // rollback 
p.release(); // explicit commit

Функция удаления из unique_ptr p возвращает ранее введенное значение, если область была оставлена ​​при активном исключении. Если вы предпочитаете явное коммит, вы можете удалить вопрос uncaugth_exception() в функции делетера и добавить в конце блока p.release(), который освобождает указатель. См. Демо здесь.

9
ответ дан kwarnke 24 August 2018 в 10:39
поделиться

Еще один ответ, но, боюсь, я нахожу других, которых так или иначе не хватает. Примечательно, что принятый ответ датируется 2012 годом, но он имеет важную ошибку (см. этот комментарий ). Это показывает важность тестирования.

Здесь представляет собой реализацию a> = C ++ 11 scope_guard, которая открыта и широко протестирована. Он предназначен для / иметь:

  • современный, элегантный, простой (в основном однофункциональный интерфейс и без макросов)
  • общий (принимает любые вызываемые, которые соблюдают предусловия)
  • тщательно задокументированная
  • тонкая обработка обратного вызова (никаких добавленных штрафов std::function или виртуальных таблиц]
  • правильные спецификации исключений

См. Также полный список функций .

0
ответ дан ricab 24 August 2018 в 10:39
поделиться

Я использую это как шарм, без дополнительного кода.

shared_ptr<int> x(NULL, [&](int *) { CloseResource(); });
1
ответ дан stu 24 August 2018 в 10:39
поделиться

Вот еще один, теперь вариант на @ kwarnke's:

std::vector< int > v{ };

v.push_back( 42 );

auto guard_handler =
[ & v ] ( nullptr_t ptr )
{
    v.pop_back( );
};

std::shared_ptr< decltype( guard_handler ) > guard( nullptr , std::move( guard_handler ) );
0
ответ дан Tarc 24 August 2018 в 10:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: