编译器如何优化临时对象?

时间:2017-07-06 16:34:24

标签: c++

我正在阅读有关move semantics的内容,并提供了以下代码:

#include <iostream>

using namespace std;

vector<int> doubleValues (const vector<int>& v)
{
    vector<int> new_values;
    new_values.reserve(v.size());
    for (auto itr = v.begin(), end_itr = v.end(); itr != end_itr; ++itr )
    {
        new_values.push_back( 2 * *itr );
    }
    return new_values;
}

int main()
{
    vector<int> v;
    for ( int i = 0; i < 100; i++ )
    {
        v.push_back( i );
    }
    v = doubleValues( v );
}

所以作者说在doubleValues返回后,最多可能发生两个副本

  一个成为临时对象返回,第二个时候返回   向量赋值运算符在第v = doubleValues(v)行上运行;

他还声明可以优化第一份副本。

这是我没有得到的

  • 在说“一个人要归还临时物品”时他的意思是什么?它不是函数返回的临时对象吗?如果是这样,我不明白为什么应该将任何东西复制到另一个临时对象。

  • 他说这个临时对象可以被优化掉。如何优化临时对象,例如什么被认为是优化?

2 个答案:

答案 0 :(得分:2)

重要的是要理解编译器必须做什么以及编译器可以做什么与正式强>意味着它意味着什么。

从您发布的代码段i++获取一个更小更简单的示例。

您的代码正式的含义是:复制i,然后增加i,并使表达式具有您之前创建的副本的值。
您的代码真正的含义是:增加i并将其余内容搞定(因为我没有使用结果)。

编译器必须做的是完全实现&#34;(代码正式意味着&#34; 部分)的(外部可见)语义。没有更多,也没有 编译器可以做的是递增i并将其余部分搞定(因为没有人可以区分)。

通常,编译器可以基于&#34;进行任何它喜欢的更改,就好像&#34;规则,这意味着,只要外部可观察的行为保持不变,没有人关心。移动语义是一种隐式和显式地进一步弯曲规则的方法。

当您移动对象而不是复制它时,您基本上是在作弊。然而,&#34;作弊&#34;和#34;魔术&#34;是你是否被抓住(就像&#34;天才&#34;&#34;疯狂&#34;之间的区别仅仅取决于成功)。
你无论如何都会滥用一个将在下一个瞬间被摧毁的物体,声称它不是一个副本。但是,没有人会注意到,因为移动的对象是一个右值,即它没有名称(因此无法访问)并且也会立即被销毁。另一方面,你呈现给世界的新物体根本不是新的,但它无论如何都是一个非常好的物体(原始物体!)。这就是整个技巧。

关于您的第二个问题:您的doubleValues函数通过常量引用获取std::vector。那不是副本,它和指针一样便宜。但是,然后将所有值复制到新对象new_values中 然后,您按值返回该对象,这意味着您正式制作第二个副本,最后您调用std::vector的赋值运算符,实际上,它甚至是第三个复制。

正式,就是这样。优化编译器可能并且希望将其优化(NRVO) 曾经是编译器只能用未命名的临时工做,但那就像... 10年前。如今,通常/经常命名的返回值优化&#34;只是运作良好。

在某些情况下(虽然不在您的示例中),编译器甚至必需按照最新标准执行copy elision

答案 1 :(得分:1)

您需要了解,如果函数返回值按值,则会通过临时返回。这意味着,非优化编译器将:

  1. new_values复制到临时的return
  2. 如果v ,则
  3. 从此临时文件复制到最后一行的main()

    该标准允许编译器删除任何这些副本,如果可以的话。例如,它可以将隐藏的引用参数传递给v中的doubleValuesdoubleValues可以使用此隐藏引用而不是new_values。这样,就不会执行任何复制。

    注意:c ++ 11编译器编译器将使用move而不是copy