我想在我的C++程序中实现一个finally
块,该语言肯定有相关的工具来实现,即使没有原生设施。我想知道最佳的方法是什么?
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
。(请参见下文。)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块实现的范畴。实现协议是包含functor的责任,所以正确的分工应该是在其中封装一个布尔标志,例如通过捕获一个bool
本地标志,并在事务完成时翻转标志。
同样,通过重新分配functor本身来取消操作只是一种混乱的方法。通常更喜欢在现有协议内部增加一个额外的情况,而不是围绕旧协议发明一个新协议。
finally(lambda)
语法后,我感到困惑,然后找到了这个问答。auto _ = finally([f] { fclose(f); }) // 记得关闭文件
参考:https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#r12-immediately-give-the-result-of-an-explicit-resource-allocation-to-a-manager-object - kevinarpeclass 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;
}
输出:
开始
抛出异常
调用终结器代码
内部捕获
外部捕获
成功!
退出
std::function
中的模板实例化开销。虽然被类型抹除隐藏了,但模板仍然存在。此外,这里的复制构造函数会导致 lambda 被调用两次。你应该删除复制构造函数,并且如果需要的话(似乎不需要),可以提供一个移动构造函数。 - Potatoswatterstd::function
内部的实例化至少包括转换构造函数(对应于工厂函数)和一个隐藏的容器类,该类提供间接调用语义(对应于哨兵类,但更复杂)。这些发生在每个不同的 lambda 中一次,或者说是“每次使用”。无论如何,现在这是一个有效的解决方案,所以 +1 :) - Potatoswatterfinally
引入了一段代码块。在C++中,它是一个对象声明。该代码在对象作用域的末尾执行,因此如果您希望在catch
之后发生,请在try
之前声明它。 - Potatoswatterexplicit
的,并且它应该移动std::function
而不是复制。如果您希望finally
完全不可复制,那么赋值运算符也应该被删除。此外,默认构造函数已经被隐式删除了。 - Emile Cormier
finally
作为本地编译器扩展来实现。 - Remy Lebeau