使用C ++ lambda正确实现finally块 [英] Correctly implement finally block using C++ lambda

查看:156
本文介绍了使用C ++ lambda正确实现finally块的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在我的C ++程序中实现最后块,并且语言肯定有工具,如果不是本机设施。我想知道最好的方法是什么?

I want to implement a finally block in my C++ program, and the language certainly has the tools to do it if not a native facility. I was wondering what the best way to do this is?

推荐答案

这个简单的实现似乎是100%安全。 >

This simple implementation seems to be 100% safe.

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 当您的函数由于异常而已经退出时,不想抛出异常。 (这导致立即终止。)C ++不检查lambda真的不能抛出任何东西;您手动检查并将其标记为 noexcept 。 (见下文)

The noexcept is important because you don't want to throw an exception when your function is already exiting due to an exception. (That leads to immediate termination.) C++ doesn't check that the lambda really can't throw anything; you manually check and mark it noexcept. (See below.)

工厂函数是必需的,否则无法获取依赖于lambda的类型。

The factory function is necessary because otherwise there's no way to obtain a type dependent on a lambda.

复制和移动构造函数必须被删除,因为它们可以用于隐式生成一个临时对象,这将实现另一个哨兵,它会在销毁时过早地调用块。但是默认的赋值操作符是完整的,因为如果你已经有两个哨兵做不同的事情,就可以分配它们。 (有点理论,但无论如何。)

The copy and move constructors must be deleted because they could be used to implicitly generate a temporary object, which would implement another sentry, which would call the block prematurely upon destruction. But the default assignment operator is left intact because if you already have two sentries that do different things, it's OK to assign them. (Somewhat theoretical, but whatever.)

如果构造函数 explicit 排除返回值的就地初始化。因为类不可移动,所以生活在调用者范围内的对象必须直接由 return 语句中的表达式初始化。

It would be nice if the constructor were explicit, but that seems to preclude in-place initialization of the return value. Since the class is not movable, the object living in the caller's scope must be initialized directly by the expression in the return statement.

要使用,只需定义一个警卫,如下:

To use, just define a guard like this:

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

绑定到引用是至关​​重要的,因为在调用范围中声明一个真实对象将需要move-从函数返回值初始化该对象。

It's essential to bind to a reference, because declaring a real object in the calling scope would require move-initializing that object from the function return value.

大写版本4.7, g ++ -Wall 防护未使用。无论您是否正在编写代码,您都可以在函数末尾添加一些安全性和文档:

Circa version 4.7, g++ -Wall would give a warning that the guard is unused. Whether or not you're coding against that, you can add a little safety and documentation at the end of the function with an idiom:

static_cast< void >( working_state_guard );

这让读者知道从范围开始执行代码,它可以作为

This lets the reader know about execution of code from the beginning of the scope, and it can serve as a reminder to double-check when copy-pasting code.

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 局部标志,在事务完成时翻转标志。


How to cancel the action from being performed?

Looking at some of the "previous attempts," I see a transactional commit() method. I don't think that belongs in the ScopeGuard/finally block implementation. Implementing a protocol is the responsibility of the contained functor, so the correct division of labor would be to encapsulate a Boolean flag in it, such as by capturing a bool local flag, and flipping the flag when the transaction is complete.

同样,尝试通过重新赋值函数来取消操作本身只是一个混乱的方法。通常喜欢现有协议中的额外情况,围绕旧协议发明一个新协议。

Likewise trying to cancel the action by reassigning the functor itself is just a messy approach. Generally prefer an extra case inside the existing protocol, to inventing a new protocol around the old one.

这篇关于使用C ++ lambda正确实现finally块的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆