关于从函数和移动返回大值的混淆

Confusion regarding returning large values from functions and move

本文关键字:返回 移动 函数      更新时间:2023-10-16

我正在观看Scott Mayers,Herb Sutter和Andrei Alexandrescu在2011年C++和超越》中的一次小组讨论。在关于哪个c ++ 11(当时的c ++ 0x(功能人们会出错的问题中,Andrei提到,当从函数返回大值时,人们认为移动语义不会涉及成本是错误的。他是这么说的

我现在不会设计接口来按值返回大东西,因为 对于所有 R 值引用,将有 在很多情况下会出现不必要的副本 创建。 这一点不应该被遗忘。

我不设计,也不容忍设计返回的接口 按价值计算的大事,因为有一天有人会从 IT和分配将是低效的。

我不认为归还大东西的成本应该被遗忘 R 值引用获胜的后果。

赫伯对此阐述了以下内容:

我同意你的看法,但它们是两种不同的情况,一种是你是 生成一个新的结果,你知道你要把它放在某个地方, 这就是你通过非常量引用传入的地方,并有一个输出 参数,这就是 out 参数的用途。

在其他情况下,您有两个输入,您将 创造新的东西,它是按价值返回的,而不是相反的 out 参数的东西,但它按值返回而不是执行 笨拙的解决方法,今天容易出错,只是堆分配 返回指针,只是为了避免额外的副本。

这是怎么回事,我只是不明白这两个家伙的意思。安德烈所说的"从中分配"的成本有什么区别?赫伯的解释,也只是在我脑海中蹦蹦跳跳。谁能详细说明一下?

另请考虑以下代码:

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

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

这是视频的链接。以上几点是在41分钟左右的播放时间后做出的。

我读不懂赫伯、安德烈或斯科特的心思。(题外话:这些家伙——都非常有才华——都与阿波罗太空计划没有任何关系(他们视频的背景((。 但是,我可以添加一些对右值引用/移动语义的见解。

如果你有一个纯工厂函数,如:

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

然后一定要按值返回它。 今天的每个编译器都会做RVO,这意味着从GetVector移动/返回的成本完全为零。

话虽如此,在一种情况下,这是不好的建议。 对于具有capacity()概念的容器(如 std::stringstd::vector(或其他一些在不影响价值的情况下增加性能的资源,可能会有一些示例,您不想无偿丢弃该资源。

例如,考虑:

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 参数重写没有任何好处。