2014-09-04 46 views
9

我有一个函数可以处理作为接收器参数传入的大量数据。我BigData类型已经是C++ 11感知,并配有功能齐全的移动构造函数和移动分配的实现,这样我就可以脱身,而无需复制该死的东西:Sink参数并移动可能失败的函数的语义(强壮的异常安全性)

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的实现和接口。然而,最终的解决方案应该尽量减少原有代码在效率和可用性方面的缺陷。

+0

重要的问题是,结果是否包含b的移动资源或仅基于它? – IdeaHat 2014-09-04 14:05:41

+0

@MadScienceDreams“Result”只是从'b'计算出来的,它并不包含对原始'b'的引用或拷贝。 – ComicSansMS 2014-09-04 14:06:56

+0

@ComicSansMS但它是否包含移动(而不是复制)的内容? – Potatoswatter 2014-09-04 14:09:05

回答

2

我对这个问题同样不感兴趣。

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

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

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

+0

我仍然不确定你为什么要传递一个RHR函数,如果它实际上并不消耗BigData的话。我想让它在将来消费它?另外,你在不可复制类型的情况下做什么(如'std :: unique_ptr')? – IdeaHat 2014-09-04 14:19:32

+0

是的,增加rvalue refs的重载在这里的确会有所帮助。您可以推迟从'BigData'移动到可以保证不会发生异常的点。当然,这受到了必须在函数接口中引入rvalue-ref重载的常见弊端(因此我并不是100%相信它满足了我的可用性约束),但是在某些情况下它可能实际上工作得很好。 +1方式。 – ComicSansMS 2014-09-04 14:29:52

+0

@MadScienceDreams 1.是的,考虑到问题中的澄清评论,我不确定它为什么不是一个“const”,但我只是在概念上回答了这个问题。 2.不可复制的类型不需要'const&'重载,就这些了。 – Potatoswatter 2014-09-04 15:05:49

2

很显然,这个问题在最近的CppCon 2014上进行了热烈的讨论。Herb Sutter在结束语中概括了最新状态Back to the Basics! Essentials of Modern C++ Style (slides)

他的结论很简单:不要对接收器参数使用传值。

首先使用这种技术的论点(如Eric Niebler的Meeting C++ 2013主题演讲C++11 Library design (slides)所推广的)似乎被缺点所掩盖。通过使用const&/&&来消除因功能超载而导致的组合爆炸,通过值传递接收器参数的初始动机是。

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

香草的结论是使用传址值水槽参数,而是依靠单独const&/&&(与const&是对于那些需要优化少数情况下保留默认值,&&)。

这也与@Potatoswatter's answer建议的相符。通过通过&&传递sink参数,我们可能会推迟数据从参数的实际移动到可以给予noexcept保证的点。

我有点喜欢按照价值传递水槽参数的想法,但它似乎在实践中并没有像所有人希望的那样好。