Подтвердить что ты не робот

Правильно реализовать блок finally, используя С++ лямбда

Я хочу реализовать блок finally в моей программе на С++, и на нем, конечно, есть инструменты для этого, если это не родной объект. Мне было интересно, что лучший способ сделать это?

4b9b3361

Ответ 1

Эта простая реализация, по-видимому, на 100% безопасна.

template< typename t >
class sentry {
    t o;
public:
    sentry( t in_o ) : o( std::move( in_o ) ) {}

    sentry( sentry && ) = delete;
    sentry( sentry const & ) = delete;

    ~ sentry() noexcept {
        static_assert( noexcept( o() ),
            "Please check that the finally block cannot throw, "
            "and mark the lambda as noexcept." );
        o();
    }
};

template< typename t >
sentry< t > finally( t o ) { return { std::move( o ) }; }

noexcept важно, потому что вы не хотите генерировать исключение, когда ваша функция уже выходит из-за исключения. (Это приводит к немедленному прекращению.) С++ не проверяет, что лямбда действительно ничего не может выбросить; вы проверите его вручную и отметьте его noexcept. (См. Ниже.)

Функция factory необходима, поскольку в противном случае нет способа получить тип, зависящий от лямбда.

Конструкторы копирования и перемещения должны быть удалены, поскольку они могут быть использованы для неявного создания временного объекта, который будет реализовывать другой часовой, который преждевременно называет блок после уничтожения. Но оператор присваивания по умолчанию остается нетронутым, потому что, если у вас уже есть два часовых устройства, которые делают разные вещи, им нужно их назначить. (Несколько теоретических, но что угодно.)

Было бы неплохо, если бы конструктор был explicit, но это, по-видимому, исключает инициализацию возвращаемого значения на месте. Поскольку класс не перемещается, объект, живущий в области вызывающего объекта, должен быть инициализирован непосредственно выражением в инструкции return.

Чтобы использовать, просто определите охрану следующим образом:

auto && working_state_guard = finally( [&]() noexcept {
    reset_working_state();
} );

Необходимо связать ссылку, потому что для объявления реального объекта в области вызова потребуется инициализация этого объекта из возвращаемого значения функции.

В версии 4.7, g++ -Wall появится предупреждение о том, что охранник не используется. Независимо от того, кодируете ли вы это, вы можете добавить небольшую безопасность и документацию в конце функции с помощью идиомы:

static_cast< void >( working_state_guard );

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


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

int main() {
    auto && guard = finally( []() noexcept {
        try {
            std::cout << "Goodbye!\n";
        } catch ( ... ) {
            // Throwing an exception from here would be worse than *anything*.
        }
    } );

    std::cin.exceptions( std::ios::failbit );
    try {
        float age;
        std::cout << "How old are you?\n";
        std::cin >> age;
        std::cout << "You are " << age << " years (or whatever) old\n";
    } catch ( std::ios::failure & ) {
        std::cout << "Sorry, didn't understand that.\n";
        throw;
    }
    static_cast< void >( guard );
}

Это производит вывод, например

$ ./sentry 
How old are you?
3
You are 3 years (or whatever) old.
Goodbye!
$ ./sentry 
How old are you?
four
Sorry, didn't understand that.
Goodbye!
terminate called after throwing an instance of 'std::ios_base::failure'
  what():  basic_ios::clear
Abort trap: 6

Как отменить действие от выполнения?

Рассматривая некоторые из "предыдущих попыток", я вижу транзакционный метод commit(). Я не думаю, что это относится к реализации блока ScopeGuard/finally. Внедрение протокола несет ответственность за содержащийся функтор, поэтому правильное разделение труда будет заключаться в инкапсуляции в него булевого флага, например, при захвате локального флага bool и переворачивании флага при завершении транзакции.

Аналогично попытка отменить действие путем переназначения самого функтора - это просто беспорядочный подход. Обычно предпочитают дополнительный случай внутри существующего протокола, изобретать новый протокол вокруг старого.

Ответ 2

Альтернативное решение, использующее std:: function. Нет функции factory. Нет шаблона для каждого использования (лучше всего?). Нет std:: move и && необходимый материал, нет необходимости в автомобиле;)

class finally
{
    std::function<void()> m_finalizer;
    finally() = delete;

public:
    finally( const finally& other ) = delete;
    finally( std::function<void()> finalizer )
     : m_finalizer(finalizer)
    {
    }
    ~finally()
    {
        std::cout << "invoking finalizer code" << std::endl;
    if( m_finalizer )
        m_finalizer();
    }
};

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

int main( int argc, char * argv[] )
{
    bool something = false;
    try
    {
    try
    {
            std::cout << "starting" << std::endl;
        finally final([&something]() { something = true; });
        std::cout << "throwing" << std::endl;
        throw std::runtime_error("boom");
    }
    catch(std::exception & ex )
    {
        std::cout << "inner catch" << std::endl;
        throw;
    }
    }
    catch( std::exception & ex )
    {
    std::cout << "outer catch" << std::endl;
        if( something )
    {
        std::cout << "works!" << std::endl;
        }
        else
    {
        std::cout << "NOT working!" << std::endl;
        }
    }
    std::cout << "exiting" << std::endl;
    return 0;
}

Вывод:

начиная

метания

вызов кода финализатора

внутренний улов

внешний catch

работает!

выход