12

我有一个函数可以对作为接收器参数传入的大量数据进行操作。我的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。然而,最终的解决方案应该尽量减少原始代码在效率和可用性方面的缺陷。

4

2 回答 2

3

我同样对这个问题感到困惑。

据我所知,目前最好的习惯用法是将传递值分成一对传递引用。

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-09-04T14:05:42.143 回答
2

显然,这个问题在最近的 CppCon 2014 上得到了热烈的讨论。Herb Sutter 在他的闭幕演讲中总结了最新的情况,回到基础!现代 C++ 风格的要点(幻灯片)

他的结论很简单:不要将值传递用于接收器参数。

首先使用这种技术的论点(正如 Eric Niebler 的 Meeting C++ 2013 主题演讲C++11 库设计(幻灯片)所宣传的那样)似乎被缺点所抵消。按值传递接收器参数的最初动机是摆脱使用const&/导致的函数重载的组合爆炸&&

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

Herb 的结论是使用传递值作为接收器参数,而是依赖单独的const&/ &&const&作为默认值,&&保留用于需要优化的少数情况)。

这也与@Potatoswatter 的回答所建议的相符。通过传递 sink 参数,&&我们可能能够将数据从参数的实际移动推迟到我们可以提供 noexcept 保证的点。

我有点喜欢按值传递接收器参数的想法,但它似乎在实践中并不像每个人希望的那样站得住脚。

考虑了5年之后更新:

我现在确信我的激励示例是对移动语义的滥用。在调用 之后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)。这种方法需要一些额外的样板,但它更惯用,因为它不需要对移出对象的状态进行假设。

于 2014-09-17T12:06:12.797 回答