繁体   English   中英

std :: move与编译器优化

[英]std::move vs. compiler optimization

例如:

void f(T&& t); // probably making a copy of t

void g() 
{
    T t;
    // do something with t
    f(std::move(t));
    // probably something else not using "t"
}

在这种情况下, void f(T const& t)等效,因为任何好的编译器都会产生相同的代码? 如果这很重要,我对> = VC10和> = GCC 4.6感兴趣。

编辑:

根据答案,我想详细说明一下这个问题:

比较rvalue-referencepass-by-value方法,很容易忘记在pass-by-value使用std::move 编译器是否仍然可以检查是否不再对变量进行更改并消除不必要的副本?

rvalue-reference方法只使优化版本“隐式”,例如f(T()) ,并要求用户明确指定其他情况,如f(std::move(t))或明确地复制f(T(t)); 如果用户没有完成t实例。 那么,在这种与优化有关的光中, rvalue-reference方法被认为是好的吗?

这绝对不一样。 一旦T && 只能绑定到rvalues,而T const &可以绑定到rvalues和lvalues。 其次, T const &不允许任何移动优化。 如果你“想要制作t的副本”,那么T &&允许你实际制作t的移动副本,这可能更有效。

例:

void foo(std::string const & s) { std::string local(s); /* ... */ }

int main()
{
    std::string a("hello");
    foo(a);
}

在这段代码中,包含"hello"的字符串缓冲区必须存在两次,一次存在于main的主体中,另一次存在于foo的主体中。 相比之下,如果您使用了右值引用和std::move(a) ,则可以“移动”相同的字符串缓冲区,并且只需要一次分配和填充。

正如@Alon指出的那样,正确的成语实际上是按价值传递

void foo(std::string local) { /* same as above */ }

int main()
{
    std::string a("hello");
    foo(std::move(a));
}

好吧,这取决于f对f的作用,如果它创建了它的副本,那么我甚至会这样做:

void f(T t) // probably making a copy of t
{
    m_newT = std::move(t); // save it to a member or take the resources if it is a c'tor..
}

void g() 
{
    T t;
    // do something with t
    f(std::move(t));
    // probably something else not using "t"
}

然后你允许移动c'tors优化发生,你在任何情况下都采用't'资源,如果它被'移动'到你的函数,那么你甚至获得了将它移动到函数的非副本,如果它没有被移动然后你可能只有一个副本

现在,如果稍后您将拥有以下代码:

f(T());

然后ta da,自由移动优化,没有f用户甚至知道..

注意引用:“在这种情况下是无效的f(T const&t)等价,因为任何好的编译器都会产生相同的代码吗?”

这不是平等的,它是无用的工作,因为只有“指针”被转移,根本没有被调用,无论是移动还是其他任何事情

采用const左值参考并采用右值参考是两回事。

相似点:

  • 也不会导致副本或移动发生,因为它们都是引用。 引用只引用一个对象,它不会以任何方式复制/移动它。

区别:

  • const lvalue引用将绑定到任何内容(lvalue或rvalue)。 右边的值可以参考只会绑定到非const右值-有限得多。

  • 当函数是一个const参考时,函数内部的参数不能被修改。 它可以当它是一个rvalue引用(因为它是非易失被修改const )。

我们来看一些例子:

  1. const lvalue reference: void f(const T& t);

    1. 通过左值:

       T t; f(t); 

      这里, t是左值表达式,因为它是对象的名称。 const lvalue引用可以绑定到任何东西,因此t将很乐意通过引用传递。 什么都没有被复制,没有任何东西被移动

    2. 传递右值:

       f(T()); 

      这里, T()是一个右值表达式,因为它创建了一个临时对象。 同样, const左值引用可以绑定到任何东西,所以这没关系。 什么都没有被复制,没有任何东西被移动

    在这两种情况下,函数内部的t都是对传入的对象的引用。它不能被引用修改为const

  2. 取rvalue参考:`void f(T && t);

    1. 通过左值:

       T t; f(t); 

      这会给你一个编译器错误。 右值引用不会绑定到左值。

    2. 传递右值:

       f(T()); 

      这很好,因为右值引用可以绑定到右值。 函数内部的引用t将引用由T()创建的临时对象。

现在让我们考虑一下std::move 首先要做的事情是: std::move实际上并没有移动任何东西。 这个想法是你给它一个左值并将它变成一个右值。 就是这样。 所以现在,如果你的f采用右值引用,你可以这样做:

T t;
f(std::move(t));

这是有效的,因为虽然t是左值,但std::move(t)是一个右值。 现在rvalue引用可以绑定到它。

那你为什么要采用右值参考参数呢? 实际上,除了定义移动构造函数和赋值运算符之外,您不需要经常这样做。 每当你定义一个带右值引用的函数时,你几乎肯定想要给出一个const左值引用重载。 它们应该几乎总是成对出现:

void f(const T&);
void f(T&&);

为什么这对功能有用? 好了,只要你给它一个左值(或第一将被称为const右值),只要你给它一个修改的右值第二个将被调用。 接收右值通常意味着你已经获得了一个临时对象,这是一个好消息,因为这意味着你可以根据你知道它不会存在更长时间的事实来破坏它的内部并进行优化。

因此,拥有这对功能可以让您在知道自己获得临时对象时进行优化。

这对函数有一个非常常见的例子:复制和移动构造函数。 它们通常定义如下:

T::T(const T&); // Copy constructor
T::T(T&&); // Move constructor

因此,移动构造函数实际上只是一个复制构造函数,它在接收临时对象时进行了优化。

当然,传递的对象并不总是临时对象。 如上所示,您可以使用std::move将左值转换为右值。 然后它似乎是该函数的临时对象。 使用std::move基本上说“我允许你将这个对象当作一个临时对象。” 它实际上是否被移动是无关紧要的。

但是,除了编写复制构造函数和移动构造函数之外,最好还是有充分的理由使用这对函数。 如果你正在编写一个接受一个对象的函数,并且无论它是否是一个临时对象,它都会与它完全相同,只需按值获取该对象即可! 考虑:

void f(T t);

T t;
f(t);
f(T());

在第一次调用f ,我们传递一个左值。 那将被复制到函数中。 在第二次调用f ,我们传递一个右值。 该对象将被移动到该函数中。 请参阅 - 我们甚至不需要使用rvalue引用来使对象有效地移动。 我们刚刚接受了价值! 为什么? 因为根据表达式是左值还是右值来选择用于进行复制/移动的构造函数 让复制/移动构造函数完成它们的工作。

至于不同的参数类型是否会产生相同的代码 - 这完全是一个不同的问题。 编译器在as-if规则下运行。 这只是意味着只要程序的行为符合标准规定,编译器就可以发出它喜欢的任何代码。 因此,如果它们碰巧完全相同,那么函数可能会发出相同的代码。 或者他们可能没有。 但是,如果你的函数采用const左值引用并且rvalue引用做同样的事情,这是一个不好的迹象。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM