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

Umm*_*mma 32 c++ optimization move-semantics c++11

据我所知,添加移动语义的目的之一是通过调用特殊构造函数来复制"临时"对象来优化代码.例如,在这个答案中我们看到它可以用来优化这些string a = x + y东西.因为x + y是一个rvalue表达式,所以我们只能复制指向字符串的指针和字符串的大小,而不是深度复制.但正如我们所知,现代编译器支持返回值优化,因此不使用移动语义,我们的代码根本不会调用复制构造函数.

为了证明这一点,我写了这段代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

在VC++ 2010中执行它并在优化模式下执行g ++后,我得到空输出.

它是什么样的优化,如果没有它,我的代码仍然可以更快地运行?你能解释一下我的理解错误吗?

Ale*_* C. 25

移动语义不应被视为优化设备,即使它们可以这样使用.

如果您想要对象的副本(函数参数或返回值),那么RVO和copy elision将尽可能地完成工作.移动语义可以提供帮助,但比这更强大.

无论传递的对象是临时对象(然后绑定到右值引用)还是带有名称的"标准"对象(所谓的const lvalue),当你想要做一些不同的事情时,移动语义都很方便.如果你想要窃取临时对象的资源,那么你想要移动语义(例如:你可以窃取内容的一个点).std::unique_ptr

移动语义允许您从函数返回不可复制的对象,这是当前标准无法实现的.此外,不可复制的对象可以放在其他对象中,如果包含的对象是这些对象,它们将自动移动.

不可复制的对象很棒,因为它们不会强迫您实现容易出错的复制构造函数.很多时候,复制语义并没有真正意义,但移动语义确实如此(想一想).

std::vector<T>即使T不可复制,这也使您可以使用可移动类.std::unique_ptr在处理不可复制的对象(例如,多态对象)时,类模板也是一个很好的工具.

  • 非常确定容器可以移动而不管它们包含什么 - 因为我们现在不需要在移动容器时触摸内部元素. (2认同)
  • @Puppy:这取决于.vector通常在堆上分配,因此你的声明似乎是正确的.但是std :: array内容是**就地**,这使得如果它的元素不支持它,则不支持移动操作,因为它不能仅仅通过指针交换来交换它的内容.然后你可以争辩说std :: array不是一个容器.但我认为应该简单地将其视为具有堆栈分配器的向量.而vector确实有灵活的分配器,因此你不能假设任何东西. (2认同)

Umm*_*mma 10

经过一番挖掘后,我在Stroustrup的常见问题解答中找到了使用右值引用进行优化的优秀示例.

是的,交换功能:

    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
}
Run Code Online (Sandbox Code Playgroud)

这将为任何类型的类型生成优化代码(假设它具有移动构造函数).

编辑: 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

此函数始终调用复制构造函数(使用VC++检查).如果我们的类可以更快地移动,那么使用移动构造函数我们将进行优化.


How*_*ant 7

想象一下,你的东西是一个像堆一样分配内存的类,并且它具有容量的概念.给它一个运算符+ =将几何增加容量.在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;
}
Run Code Online (Sandbox Code Playgroud)

还想象你想要一次添加两个以上的东西:

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";
}
Run Code Online (Sandbox Code Playgroud)

对我来说这打印出来:

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
Run Code Online (Sandbox Code Playgroud)

我计算3个分配和2个解除分配来计算:

stuff e = a+b+c+d;
Run Code Online (Sandbox Code Playgroud)

现在添加移动语义:

    stuff(stuff&& g):size(g.size), cap(g.cap)
    {
        g.cap = 0;
        g.size = 0;
    }
Run Code Online (Sandbox Code Playgroud)

...

stuff operator+(stuff&& lhs,const stuff& rhs)
{
        return std::move(lhs += rhs);
}
Run Code Online (Sandbox Code Playgroud)

再跑一次我得到:

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
Run Code Online (Sandbox Code Playgroud)

我现在已经减少了2次分配和1次解除分配.这转化为更快的代码.