可能失败的函数的接收参数和移动语义(强异常安全性)
Sink arguments and move semantics for functions that can fail (strong exception safety)
我有一个函数,它对作为sink参数传入的一大块数据进行操作。我的BigData
类型已经支持C++11,并且带有功能齐全的移动构造函数和移动赋值实现,所以我可以不用复制该死的东西:
Result processBigData(BigData);
[...]
BigData b = retrieveData();
Result r = processBigData(std::move(b));
这一切都很好。但是,我的处理函数可能会在运行时偶尔失败,从而导致异常。这不是一个真正的问题,因为我可以修复东西并重试:
BigData b = retrieveData();
Result r;
try {
r = processBigData(std::move(b));
} catch(std::runtime_error&) {
r = fixEnvironmnentAndTryAgain(b);
// wait, something isn't right here...
}
当然,这是行不通的。
由于我已将数据移动到处理函数中,所以当我到达异常处理程序时,b
将不再可用。
这可能会大大降低我通过价值传递水槽论点的热情。
所以问题来了:在现代C++代码中,如何处理这样的情况?如何检索对以前移动到未能执行的函数中的数据的访问权限?
您可以根据需要更改BigData
和processBigData
的实现和接口。然而,最终的解决方案应该尽量减少原始代码在效率和可用性方面的缺陷。
我同样对这个问题感到困惑。
据我所知,目前最好的习惯用法是将pass-by值划分为一对pass-by引用。
template< typename t >
std::decay_t< t >
val( t && o ) // Given an object, return a new object "val"ue by move or copy
{ return std::forward< t >( o ); }
Result processBigData(BigData && in_rref) {
// implementation
}
Result processBigData(BigData const & in_cref ) {
return processBigData( val( in_cref ) );
}
当然,在出现异常之前,可能已经移动了参数的各个部分。问题传播到processBigData
调用的任何对象。
我得到了一个灵感,开发了一个在某些例外情况下将自己移回源的对象,但这是我的一个项目中即将出现的特定问题的解决方案。它可能最终过于专业化,也可能根本不可行。
显然,这个问题在最近的2014年CppCon上得到了热烈讨论。赫伯·萨特在他的闭幕词《回到基础!现代C++风格要点(幻灯片)。
他的结论很简单:不要对sink参数使用pass-by-value
首先使用这种技术的论点(正如Eric Niebler的Meeting C++2013主题演讲C++11 Library design(幻灯片)所推广的那样)似乎被缺点所压倒。按值传递sink参数的最初动机是为了消除由于使用const&
/&&
而导致的函数重载的组合爆炸。
不幸的是,这似乎带来了一些意想不到的后果。其中之一是潜在的效率缺陷(主要是由于不必要的缓冲区分配)。另一个是这个问题中的异常安全问题。赫伯的演讲中讨论了这两个问题。
Herb的结论是不使用传递值作为sink参数,而是依赖于单独的const&
/&&
(默认为const&
,&&
保留用于需要优化的少数情况)。
这也符合@Potatoswatter的回答。通过&&
传递sink参数,我们可能能够将数据从参数的实际移动推迟到可以提供noexcept保证的点。
我有点喜欢按价值传递水槽论点的想法,但它在实践中似乎并不像每个人希望的那样有效。
思考了5年后的更新:
我现在确信,我的激励性例子是对move语义的滥用。在调用processBigData(std::move(b));
之后,我永远不应该被允许假设b
的状态是什么,即使函数异常退出。这样做会导致代码难以遵循和维护。
相反,如果b
的内容在错误情况下应该是可恢复的,则需要在代码中明确这一点。例如:
class BigDataException : public std::runtime_error {
private:
BigData b;
public:
BigData retrieveDataAfterError() &&;
// [...]
};
BigData b = retrieveData();
Result r;
try {
r = processBigData(std::move(b));
} catch(BigDataException& e) {
b = std::move(e).retrieveDataAfterError();
r = fixEnvironmnentAndTryAgain(std::move(b));
}
如果我想恢复b
的内容,我需要沿着错误路径显式地传递它们(在本例中,封装在BigDataException
中)。这种方法需要一些额外的样板,但它更惯用,因为它不需要对移动的对象的状态进行假设。
- 处理多个异常集合的C++方法
- 我在c++代码中生成了一个运行时#3异常
- 何时在引用或唯一指针上使用移动语义
- 孤立代码块在结构中引发异常
- C++中的赋值发生,尽管右侧出现异常
- 如何从具有移动语义的类对象中生成共享指针
- 从构造函数抛出异常时如何克服内存泄漏
- 异常属于C++中的线程还是进程
- 当类定义不可见时捕获异常
- Boost Spirit,获取迭代器内部语义动作
- 引发异常:读取访问冲突**dynamicArray**为0x1118235.发生
- 为什么异常不退出程序?
- 为什么我应该在异常处理中使用std::cerr而不是std::cout
- 可以使用移动语义更改或改进此C++代码吗?
- 如何修复链表类实现的未处理异常0xDDDDDDDD
- 视觉 std::矢量无异常:警告 C4530:使用了C++异常处理程序,但未启用展开语义.指定 /EHsc
- 移动语义行为异常
- 异常安全代码和移动语义
- 异常、移动语义和优化:任由编译器摆布 (MSVC2010)?
- 可能失败的函数的接收参数和移动语义(强异常安全性)