关于从函数返回大值并移动的困惑

时间:2014-12-09 00:35:52

标签: c++ c++11 move-semantics rvalue-reference

我正在观看Scott Mayers,Herb Sutter和来自C ++ And Beyond 2011的Andrei Alexandrescu之间的旧小组讨论。关于哪个c ++ 11(当时的c ++ 0x)功能人们会得到一个问题错了,安德烈提到人们假设移动语义在从函数返回大值时不会产生成本是错误的。这就是他所说的

  

我现在不会设计接口以按值返回大的东西   对于所有r值参考,纯粹的原因是有的   很多情况下都会有不必要的副本   创建。这不应该被遗忘。

     

我没有设计,我也不会宽恕设计返回的界面   有价值的大事,因为有一天有人要分配   它和任务将是低效的。

     

我不认为回归大事的成本应该被遗忘   r值参考胜利的后果。

Herb详细阐述了以下内容:

  

我同意你的意见,但他们是两种不同的情况,一种是你   生成一个你知道要放在某个地方的新结果,   那是你通过非const引用传入的地方,并且有一个out   参数,这是参数的用途。

     

还有其他情况,你有两个输入,你会去   创造新的东西,在那里它是价值回报,而不是相反   out参数的东西,但它按值返回而不是做   今天容易出错的笨重的解决方法,只是堆分配   返回指针,只是为了避免额外的副本。

这里发生了什么,我只是理解这些家伙正在制造的这一点。 "从中分配"的成本差异是什么?安德烈在谈论什么? 而Herb的解释也只是在我脑海中反弹。有人可以详细说明吗?

另请考虑以下代码:

vector<BigData> GetVector(int someIndex)
{
   vector<BigData> toFill;
   // some processing
   // filling the vector
   return toFill;
}

我认为移动语义会使上面的代码等同于将空向量作为out参数传递。这不是吗?

以下是video的链接。以上几点是在41分钟左右的比赛时间后完成的。

1 个答案:

答案 0 :(得分:3)

我无法读懂赫伯,安德烈或斯科特的思想。 (旁白:这些人都没有 - 都非常有才华 - 与阿波罗太空计划(他们视频中的背景)有任何关系)。但是我可以对rvalue-reference / move-semantics添加一些见解。

如果你有一个纯粹的工厂功能,如:

vector<BigData>
GetVector(int someIndex)
{
   vector<BigData> toFill;
   // some processing
   // filling the vector
   return toFill;
}

然后肯定按价值返回。今天的每个编译器都会执行RVO,这意味着从GetVector移动/返回完全零成本。

这就是说,有一个背景,这是不好的建议。对于std::stringstd::vector等概率为capacity()的容器,或其他一些在不影响值的情况下增加性能的资源,可能会出现一些您不想抛出的示例无偿地离开那个资源。

例如,考虑:

vector<BigData> data;
while (I_need_to)
{
    data = GetVector(someIndex);
    process(data);
}

在此示例中,每次循环时,GetVector都会分配新的vector,这可能会造成浪费,因为vector具有可在循环中重用的容量像这样。考虑一下这个重写:

void
GetVector(int someIndex, vector<BigData>& toFill)
{
   toFill.clear();
   // some processing
   // filling the vector
   return toFill;
}
// ...
vector<BigData> data;
while (I_need_to)
{
    GetVector(someIndex, data);
    process(data);
}

在此重写中,data每次循环时仍会获得一个新值。但不同之处在于保存了前一个循环中的capacity(),并重新用于当前循环。如果此循环所需的data.size()小于前一循环的data.capacity(),则当前循环永远不需要进行重新分配。减少到堆的访问是提高效率的关键。实际上,减少对堆的访问是语义学的全部内容。

猜测这是视频中讨论的要点。

我应该强调:如果工厂函数不会在这样的循环中使用,或者工厂函数的返回类型不能使用某些先前的资源(如capacity()) ,然后使用inOut参数重写没有任何好处。