使用C++lambda正确实现finally块
Correctly implement finally block using C++ lambda
我想在C++程序中实现一个finally
块,如果不是本机工具的话,该语言当然有工具可以实现。我想知道最好的方法是什么?
这个简单的实现似乎是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
很重要,因为当函数已经因异常而退出时,您不想抛出异常。(这会导致立即终止。)C++不会检查lambda是否真的不能抛出任何东西;手动检查并标记为noexcept
。(见下文。)
工厂函数是必要的,因为否则就无法获得依赖于lambda的类型。
复制和移动构造函数必须被删除,因为它们可以用于隐式生成一个临时对象,该对象将实现另一个哨兵,该哨兵将在销毁时提前调用块。但默认的赋值运算符保持不变,因为如果您已经有两个哨兵做不同的事情,那么可以对它们进行赋值。(有点理论,但不管怎样。)
如果构造函数是explicit
,那就太好了,但这似乎排除了对返回值的就地初始化。由于类是不可移动的,因此必须通过return
语句中的表达式直接初始化调用方作用域中的对象。
要使用,只需定义这样的防护:
auto && working_state_guard = finally( [&]() noexcept {
reset_working_state();
} );
绑定到引用是至关重要的,因为在调用范围中声明一个真实对象需要从函数返回值移动初始化该对象。
Circa 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) oldn";
} 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
本地标志,并在事务完成时翻转该标志。
同样,试图通过重新分配函子本身来取消操作也是一种混乱的方法。一般来说,比起围绕旧协议发明新协议,更喜欢在现有协议中增加一个用例。
使用std::函数的替代解决方案。不需要工厂功能。没有每次使用的模板实例化(占地面积更好?!)。无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;
}
输出:
启动
投掷
调用终结器代码
内部捕获
外部捕获
工作!
退出
- 如果没有malloc,链表实现将失败
- 如何在c++中实现处理器调度模拟器
- 如何在c++中使用引用实现类似python的行为
- 实现无开销push_back的最佳方法是什么
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- 实现一个在集合上迭代的模板函数
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 如何正确实现和访问运算符的各种自定义枚举器
- C++Union/Struct位域的实现和可移植性
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 在c++中实现LinkedList时,应出现未处理的错误
- 为左值和右值的包装器实现C++范围
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- 使用GSoap实现ONVIF
- 在用于格式4的arm模拟器中实现功能时的一个问题
- 用于AVX的ln(x)的实现,m256
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在C++中,如何在类和函数(可能是模板化的)的头中编写完整的实现
- 使用C++lambda正确实现finally块