接收参数并移动可能失败的函数的语义(强异常安全性)

时间:2014-09-04 13:53:10

标签: c++ c++11 move-semantics idioms exception-safety

我有一个函数可以作为接收器参数传入大量数据。我的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 ++代码中处理这样的情况?如何检索以前移入无法执行的函数的数据访问?

您可以根据需要更改BigDataprocessBigData的实施和界面。然而,最终的解决方案应该尽量减少原始代码在效率和可用性方面的缺点。

2 个答案:

答案 0 :(得分:3)

我同样对这个问题感到不安。

据我所知,当前最好的习惯用法是将pass-by-value分成一对传递引用。

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次调用。

我有一个灵感来开发一个在某些例外情况下将自己移回其源头的对象,但这是我的一个项目中即将解决的特定问题的解决方案。它可能最终过于专业化,或者根本不可行。

答案 1 :(得分:3)

显然这个问题在最近的CppCon 2014中得到了热烈的讨论.Herb Sutter在他的闭幕式Back to the Basics! Essentials of Modern C++ Style (slides)中总结了最新的事态。

他的结论非常简单:不要使用pass-by-value作为接收器参数。

首先使用这种技术的论据(正如Eric Niebler的会议C ++ 2013主题演讲C++11 Library design (slides)所推广的)似乎被这些缺点所抵消。传递sink参数by-value的最初动机是摆脱因使用const& / &&而导致的函数重载的组合爆炸。

不幸的是,这似乎带来了许多意想不到的后果。其中一个是潜在的效率缺陷(主要是由于不必要的缓冲区分配)。另一个是这个问题的异常安全问题。这两个都在赫伯的谈话中讨论过。

Herb的结论是使用pass-by-value来接收参数,而是依赖于单独的const& / &&({{1}作为默认值,const&保留用于需要优化的少数情况。)

这也与@Potatoswatter's answer建议的内容相匹配。通过&&传递sink参数,我们可以将数据的实际移动推迟到我们可以给出noexcept保证的点。

我有点喜欢通过值传递接收参数的想法,但似乎它在实践中并没有像大家所希望的那样好。