简体   繁体   English

如果我们已经有了RVO,移动语义将提供什么优化?

[英]What optimization does move semantics provide if we already have RVO?

As far as I understand one of the purposes of adding move semantics is to optimize code by calling special constructor for copying "temporary" objects. 据我了解,添加移动语义的目的之一是通过调用特殊的构造函数来复制“临时”对象来优化代码。 For example, in this answer we see that it can be used to optimize such string a = x + y stuff. 例如,在这个答案中,我们看到它可以用于优化这样的string a = x + y东西。 Because x+y is an rvalue expression, instead of deep copying we can copy only the pointer to the string and the size of the string. 因为x + y是一个右值表达式,所以除了深度复制,我们只能复制指向字符串的指针和字符串的大小。 But as we know, modern compilers support return value optimization , so without using move semantics our code will not call the copy constructor at all. 但是,众所周知,现代编译器支持返回值优化 ,因此,如果不使用移动语义,我们的代码将根本不会调用复制构造函数。

To prove it I write this code: 为了证明这一点,我编写了这段代码:

#include <iostream>

struct stuff
{
        int x;
        stuff(int x_):x(x_){}
        stuff(const stuff & g):x(g.x)
        {
                std::cout<<"copy"<<std::endl;
        }
};   
stuff operator+(const stuff& lhs,const stuff& rhs)
{
        stuff g(lhs.x+rhs.x);
        return g;
}
int main()
{
        stuff a(5),b(7);
        stuff c = a+b;
}

And after executing it in VC++2010 and g++ in optimize mode I'm getting empty output. 在VC ++ 2010和g ++中以优化模式执行它之后,我得到的输出为空。

What kind of optimization is it, if without it my code still works faster? 如果没有它,我的代码仍然可以更快地运行,它是一种什么样的优化? Could you explain what I'm understanding wrong? 你能解释我所理解的错误吗?

Move semantics should not be thought as an optimization device, even if they can be used as such. 即使可以将移动语义本身用作移动语义,也不应将其视为优化手段。

If you are going to want copies of objects (either function parameters or return values), then RVO and copy elision will do the job when they can. 如果要复制对象(函数参数或返回值),则RVO和复制省略将在可能的情况下完成工作。 Move semantics can help, but are more powerful than that. 移动语义可以提供帮助,但功能更强大。

Move semantics are handy when you want to do something different whether the passed object is a temporary (it then binds to a rvalue reference ) or a "standard" object with a name (a so called const lvalue ). 当你想要做不同的事情传递的对象是否是临时的(它,然后绑定到一个右值引用 )或名称(所谓的常量左值 )“标准”对象移动语义是得心应手。 If you want for instance to steal the resources of a temporary object, then you want move semantics (example: you can steal the contents a std::unique_ptr points to). 例如,如果要窃取临时对象的资源 ,则需要移动语义(例如:您可以窃取std::unique_ptr指向的内容)。

Move semantics allow you to return non copyable objects from functions, which is not possible with the current standard. 移动语义允许您从函数返回不可复制的对象,而当前标准则无法实现。 Also, non copyable objects can be put inside other objects, and those objects will automatically be movable if the contained objects are. 另外,不可复制的对象可以放在其他对象中,如果包含的对象在其中,则这些对象将自动移动。

Non copyable objects are great, since they don't force you to implement an error-prone copy constructor. 不可复制对象很棒,因为它们不会强迫您实现易于出错的复制构造函数。 A lot of the time, copy semantics do not really make sense, but move semantics do (think about it). 很多时候,复制语义并没有真正的意义,但移动语义确实有意义(考虑一下)。

This also enables you to use movable std::vector<T> classes even if T is non copyable. 即使T是不可复制的,这也使您能够使用可移动的std::vector<T>类。 The std::unique_ptr class template is also a great tool when dealing with non copyable objects (eg. polymorphic objects). 当处理不可复制的对象(例如,多态对象)时, std::unique_ptr类模板也是一个很好的工具。

After some digging I find this excellent example of optimization with rvalue references in Stroustrup's FAQ . 经过一番挖掘之后,我在Stroustrup的FAQ中找到了一个使用右值引用进行优化的出色示例。

Yes, swap function: 是的,交换功能:

    template<class T> 
void swap(T& a, T& b)   // "perfect swap" (almost)
{
    T tmp = move(a);    // could invalidate a
    a = move(b);        // could invalidate b
    b = move(tmp);      // could invalidate tmp
}

This will generate optimized code for any kind of types (assuming, that it have move constructor). 这将为任何类型的类型生成优化的代码(假设它具有move构造函数)。

Edit: Also RVO can't optimize something like this(at least on my compiler): 编辑:同样,RVO无法优化这样的东西(至少在我的编译器上):

stuff func(const stuff& st)
{
    if(st.x>0)
    {
        stuff ret(2*st.x);
        return ret;
    }
    else
    {
        stuff ret2(-2*st.x);
        return ret2;
    }
}

This function always calls copy constructor (checked with VC++). 此函数始终调用复制构造函数(已通过VC ++检查)。 And if our class can be moved faster, than with move constructor we will have optimization. 如果我们的类可以比移动构造器更快地移动,那么我们将进行优化。

Imagine your stuff was a class with heap allocated memory like a string, and that it had the notion of capacity. 想象一下,您的东西是一个类,它为堆分配了像字符串一样的内存,并且它具有容量的概念。 Give it a operator+= that will grow the capacity geometrically. 给它一个operator + =,它将以几何方式增加容量。 In C++03 this might look like: 在C ++ 03中,它可能类似于:

#include <iostream>
#include <algorithm>

struct stuff
{
    int size;
    int cap;

    stuff(int size_):size(size_)
    {
        cap = size;
        if (cap > 0)
            std::cout <<"allocating " << cap <<std::endl;
    }
    stuff(const stuff & g):size(g.size), cap(g.cap)
    {
        if (cap > 0)
            std::cout <<"allocating " << cap <<std::endl;
    }
    ~stuff()
    {
        if (cap > 0)
            std::cout << "deallocating " << cap << '\n';
    }

    stuff& operator+=(const stuff& y)
    {
        if (cap < size+y.size)
        {
            if (cap > 0)
                std::cout << "deallocating " << cap << '\n';
            cap = std::max(2*cap, size+y.size);
            std::cout <<"allocating " << cap <<std::endl;
        }
        size += y.size;
        return *this;
    }
};

stuff operator+(const stuff& lhs,const stuff& rhs)
{
    stuff g(lhs.size + rhs.size);
    return g;
}

Also imagine you want to add more than just two stuff's at a time: 还要想象一下,您想一次添加两个以上的东西:

int main()
{
    stuff a(11),b(9),c(7),d(5);
    std::cout << "start addition\n\n";
    stuff e = a+b+c+d;
    std::cout << "\nend addition\n";
}

For me this prints out: 对我来说,它打印出来:

allocating 11
allocating 9
allocating 7
allocating 5
start addition

allocating 20
allocating 27
allocating 32
deallocating 27
deallocating 20

end addition
deallocating 32
deallocating 5
deallocating 7
deallocating 9
deallocating 11

I count 3 allocations and 2 deallocations to compute: 我计算了3个分配和2个释放来计算:

stuff e = a+b+c+d;

Now add move semantics: 现在添加移动语义:

    stuff(stuff&& g):size(g.size), cap(g.cap)
    {
        g.cap = 0;
        g.size = 0;
    }

... ...

stuff operator+(stuff&& lhs,const stuff& rhs)
{
        return std::move(lhs += rhs);
}

Running again I get: 再次运行,我得到:

allocating 11
allocating 9
allocating 7
allocating 5
start addition

allocating 20
deallocating 20
allocating 40

end addition
deallocating 40
deallocating 5
deallocating 7
deallocating 9
deallocating 11

I'm now down to 2 allocations and 1 deallocations. 我现在只有2个分配和1个解除分配。 That translates to faster code. 转化为更快的代码。

There are many places some of which are mentioned in other answers. 在许多其他答案中提到了许多地方。

One big one is that when resizing a std::vector it will move move-aware objects from the old memory location to the new one rather than copy and destroy the original. 一个很大的问题是,调整std::vector大小时,它将可移动对象从旧的内存位置移到新的位置,而不是复制并销毁原始对象。

Additionally rvalue references allow the concept of movable types, this is a semantic difference and not just an optimization. 另外,右值引用允许使用可移动类型的概念,这是语义上的差异,而不仅仅是优化。 unique_ptr wasn't possible in C++03 which is why we had the abomination of auto_ptr . 在C ++ 03中不可能使用unique_ptr ,这就是为什么我们讨厌auto_ptr

Just because this particular case is already covered by an existing optimization does not mean that other cases don't exist where r-value references are helpful. 仅仅因为这种特殊情况已经被现有优化所涵盖并不意味着在r值引用有用的情况下就不存在其他情况。

Move construction allows optimization even when the temporary is returned from a function which cannot be inlined (perhaps it's a virtual call, or through a function pointer). 即使无法从内联的函数返回临时属性(也许是虚拟调用或通过函数指针),通过移动构造也可以进行优化。

Your posted example only takes const lvalue references and so explicitly cannot have move semantics applied to it, as there is not a single rvalue reference in there. 您发布的示例仅接受const左值引用,因此显式不能对其应用移动语义,因为其中没有单个右值引用。 How can move semantics make your code faster when you implemented a type without rvalue references? 当实现没有右值引用的类型时,移动语义如何使您的代码更快?

In addition, your code is already covered by RVO and NRVO. 此外,RVO和NRVO已经覆盖了您的代码。 Move semantics apply to far, far more situations than those two do. 移动语义适用于远远超过这两种情况。

This line calls the first constructor. 这行调用第一个构造函数。

stuff a(5),b(7);

Plus operator is called using explicit common lvalue references. 使用显式通用左值引用调用加号运算符。

stuff c = a + b;

Inside operator overload method, you have no copy constructor called. 在运算符重载方法内部,没有调用副本构造函数。 Again, the first constructor is called only. 同样,仅调用第一个构造函数。

stuff g(lhs.x+rhs.x);

assigment is made with RVO, so no copy is need. 使用RVO进行分配,因此无需复制。 NO copy from returned object to 'c' is need. 无需从返回的对象复制到“ c”。

stuff c = a+b;

Due no std::cout reference, compiler take care about your c value is never used. 由于没有std::cout引用,编译器会小心不要使用c值。 Then, whole program is stripped out, resulting in a empty program. 然后,整个程序被剥离,从而导致一个空程序。

Another good example I can think of. 我能想到的另一个很好的例子。 Imagine that you're implementing a matrix library and write an algorithm which takes two matrices and outputs another one: 想象一下,您正在实现一个矩阵库,并编写一个采用两个矩阵并输出另一个矩阵的算法:

Matrix MyAlgorithm(Matrix U, Matrix V)
{
    Transform(U); //doesn't matter what this actually does, but it modifies U
    Transform(V);
    return U*V;
}

Note that you can't pass U and V by const reference, because the algorithm tweaks them. 请注意,您不能通过const引用传递U和V,因为该算法会对其进行调整。 You could theoretically pass them by reference, but this would look gross and leave U and V in some intermediate state (since you call Transform(U) ), which may not make any sense to the caller, or just not make any mathematical sense at all, since it's just one of the internal algorithm transformations. 从理论上讲,您可以通过引用传递它们,但这看起来很粗糙,并且使UV处于某种中间状态(因为您调用了Transform(U) ),这对调用者可能没有任何意义,或者在调用时没有任何数学意义。所有,因为这只是内部算法转换之一。 The code looks much cleaner if you just pass them by value and use move semantics if you are not going to use U and V after calling this function: 如果仅按值传递它们,并在调用此函数后不使用UV ,则使用move语义可以使代码看起来更简洁:

Matrix u, v;
...
Matrix w = MyAlgorithm(u, v); //slow, but will preserve u and v
Matrix w = MyAlgorithm(move(u), move(v)); //fast, but will nullify u and v
Matrix w = MyAlgorithm(u, move(v)); //and you can even do this if you need one but not the other

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

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