【问题标题】:Confusion regarding returning large values from functions and move关于从函数返回大值和移动的困惑
【发布时间】:2014-12-09 00:35:52
【问题描述】:

我正在观看来自 C++ And Beyond 2011 的 Scott Mayers、Herb Sutter 和 Andrei Alexandrescu 之间的一个老小组讨论。其中关于人们将获得哪些 c++11(当时的 c++0x)特性的问题错了,Andrei 提到人们假设从函数返回大值时移动语义不会涉及成本是错误的。这就是他所说的

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

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

我认为不应该忘记退货的成本 r 值引用胜利的后果。

Herb 详细阐述了以下内容:

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

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

这里发生了什么,我只是不明白这两个人的意思。 安德烈所说的“从它分配”的成本有什么区别? Herb 的解释也在我脑海中浮现。谁能详细说明一下?

还请考虑以下代码:

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

我认为移动语义将使上述代码等效于将空向量作为输出参数传递。不是这样吗?

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

【问题讨论】:

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


    【解决方案1】:

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

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

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

    【讨论】:

    • 我同意你的观察霍华德,这确实可能是这些家伙的意思,但你说这是你的“猜测”......我希望一些读心者会过来并给出答案在我的脑海中点燃一个火炬!
    • Fwiw,我的“猜测”在移动语义部门相当不错:open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-05
    • 2018-04-26
    • 2015-04-08
    • 2014-03-22
    • 1970-01-01
    • 2016-10-30
    • 1970-01-01
    相关资源
    最近更新 更多