如果我们已经拥有RVO,移动语义会提供哪些优化?
据我了解,添加移动语义的目的之一是通过调用用于复制“临时”对象的特殊构造函数来优化代码。 例如,在这个答案中,我们看到它可以用来优化这种string a = x + y
东西。 因为x + y是一个右值表达式,所以我们只能将指针复制到字符串和字符串的大小,而不是深度复制。 但是,正如我们所知,现代编译器支持返回值优化,所以如果不使用移动语义,我们的代码根本不会调用复制构造函数。
为了证明它,我编写了这段代码:
#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;
}
然后在VC ++ 2010和g ++中以优化模式执行它后,我得到空输出。
什么样的优化,如果没有它,我的代码仍然工作更快? 你能解释我认识的错吗?
移动语义不应被认为是一种优化设备,即使它们可以这样使用。
如果您想要获得对象的副本(函数参数或返回值),那么RVO和copy elision将尽其所能完成这项工作。 移动语义可以提供帮助,但比这更强大。
当你想要做一些不同的事情时,移动语义很方便,不管传递的对象是临时的(它绑定到右值引用)还是带有名称的“标准”对象(所谓的const左值)。 如果你想要例如窃取一个临时对象的资源 ,那么你需要移动语义(例如:你可以窃取std::unique_ptr
指向的内容)。
移动语义允许您从函数中返回不可复制的对象,这是当前标准无法实现的。 而且,不可复制的对象可以放在其他对象中,如果包含的对象是这些对象,它们将自动移动。
不可复制的对象很好,因为它们不会强制你实现一个容易出错的复制构造函数。 很多时候,复制语义并不是真的有意义,但是移动语义做(考虑它)。
这也使您可以使用可移动的std::vector<T>
类,即使T
是不可复制的。 在处理不可复制的对象(如多态对象)时, std::unique_ptr
类模板也是一个很好的工具。
经过一番挖掘,我发现这个优秀的例子是在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
}
这将为任何类型的类型生成优化的代码(假设它具有移动构造函数)。
编辑:另外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;
}
}
此函数始终调用复制构造函数(使用VC ++进行检查)。 如果我们的类可以更快地移动,比移动构造函数我们将有优化。
想象一下,你的东西是一个堆类,像字符串一样分配内存,它具有容量的概念。 给它一个运算符+ =,它将以几何形式增长容量。 在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;
}
另外想象一下,你想添加的不仅仅是两件东西:
int main()
{
stuff a(11),b(9),c(7),d(5);
std::cout << "start additionnn";
stuff e = a+b+c+d;
std::cout << "nend additionn";
}
对我来说这打印出来:
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
我计算3次分配和2次解除分配来计算:
stuff e = a+b+c+d;
现在添加移动语义:
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);
}
再次运行我得到:
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
我现在下降到2个分配和1个释放。 这意味着更快的代码。
链接地址: http://www.djcxy.com/p/40323.html上一篇: What optimization does move semantics provide if we already have RVO?