使用C++ lambda正确实现finally块

Pot*_*ter 27 c++ finally c++11

我想finally在我的C++程序中实现一个块,如果不是原生设施,那么该语言肯定有工具可以实现.我想知道最好的方法是什么?

Pot*_*ter 35

这个简单的实现似乎是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 ) }; }
Run Code Online (Sandbox Code Playgroud)

noexcept很重要,因为当您的函数因异常而退出时,您不希望抛出异常.(这导致立即终止.)C++没有检查lambda是否真的不能抛出任何东西; 你手动检查并标记它noexcept.(见下文.)

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

必须删除复制和移动构造函数,因为它们可以用于隐式生成临时对象,这将实现另一个哨兵,它会在销毁时过早地调用块.但默认赋值运算符保持不变,因为如果您已经有两个执行不同操作的句子,则可以分配它们.(有点理论,但无论如何.)

如果构造函数是explicit,那将是很好的,但这似乎排除了返回值的就地初始化.由于类不可移动,因此必须通过return语句中的表达式直接初始化生成在调用者范围内的对象.

要使用,只需定义一个这样的守卫:

auto && working_state_guard = finally( [&]() noexcept {
    reset_working_state();
} );
Run Code Online (Sandbox Code Playgroud)

绑定到引用是必不可少的,因为在调用范围中声明一个真实对象需要从函数返回值初始化该对象.

大约版本4.7,g++ -Wall将发出警告,警卫未使用.无论您是否正在编写代码,您都可以在函数末尾添加一些安全和文档,并带有一个成语:

static_cast< void >( working_state_guard );
Run Code Online (Sandbox Code Playgroud)

这使读者可以从作用域的开头了解代码的执行情况,并且可以作为提醒,以便在复制粘贴代码时进行复核.


用法.

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 );
}
Run Code Online (Sandbox Code Playgroud)

这会产生类似的输出

$ ./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
Run Code Online (Sandbox Code Playgroud)

如何取消正在执行的操作?

看一些"以前的尝试",我看到了一个事务commit()方法.我认为这不属于ScopeGuard/finally块实现.实现协议是包含的仿函数的责任,因此正确的分工是在其中封装布尔标志,例如通过捕获bool本地标志,并在事务完成时翻转标志.

同样试图通过重新分配仿函数来取消动作只是一种混乱的方法.通常更喜欢现有协议中的额外案例,以围绕旧协议发明新协议.


Bit*_*ler 7

使用std :: function的替代解决方案.无需工厂功能.没有按使用模板实例化(更好的足迹?!).不需要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();
    }
};
Run Code Online (Sandbox Code Playgroud)

用法:

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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

开始

投掷

调用终结器代码

内部捕获

外部捕获

作品!

退出

  • 在其他语言中,`finally`引入了一段代码.在C++中,它是一个对象声明.代码在对象范围的末尾执行,所以如果你想在`catch`之后发生它,只需在`try`之前声明它. (2认同)