在上一篇文章中,我们可知,在vector进行扩容或缩容的时候,元素的拷贝是不可避免的。既然拷贝是不可避免的,那么能不能较低拷贝的开销哪?
C++11中很大的一个特性是移动语义,移动语义可以将资源“偷”过来,避免了资源的拷贝和释放。在类有很大的资源时,使用移动语义可以大幅的提升类构造和赋值的性能。
接下来分析一下,移动语义是如何提升vector性能的。
定义一个用于测试的类
class A
{
public:
A()
{
cout << "constuctor :" << this << endl;
const char * s = "hello";
int len = strlen(s);
str_ = new char[len + 1];
strcpy(str_, s);
}
A(const A &another)
{
cout << this << " cpy constructor from " << &another << endl;
int len = strlen(another.str_);
str_ = new char[len + 1];
strcpy(str_, another.str_);
}
A(A &&another) noexcept
{
cout << this << " move constructor from " << &another << endl;
this->str_ = another.str_;
another.str_ = nullptr;
}
~A()
{
cout << "deconstrutor :" << this << endl;
if (str_)
{
delete[] str_;
}
}
private:
char *str_;
};
没有移动构造时
构造vector对象
A a;
vector<A> va(3,a);
vector<A> va(3,a);
执行完毕后,vector中有三个元素,且每个元素都有资源。
向vector中推入对象a
A a;
vector<A> va(3,a);
cout<<"============================"<<endl;
va.push_back(a);
推入a元素之前,va并没有多余的空间,所以为了存放a元素,需要再开辟一段空间,将a元素推进去,同时还需要将之前的三个元素也拷贝过去。
在拷贝之前的三个元素时,会造成资源的拷贝和释放。
提供移动构造时
构造vector对象
A a;
vector<A> va(3,a);
向vector中推入对象a
A a;
vector<A> va(3,a);
cout<<"============================"<<endl;
va.push_back(a);
可以看到,在将旧空间中的三个元素搬移到新空间时,使用的是移动构造而不是拷贝构造。在移动构造时,直接将旧元素的资源给搬移过来了,而没有发生资源的拷贝和释放。
vector的空间在扩容时,需要将旧空间的元素复制到新的空间,使用移动构造,可以将旧空间中,旧元素的资源给“偷”到新空间的元素中,这样就可以避免资源的拷贝和释放,可以极大的提高性能。
移动构造必须加上noexcept
A(A &&another) noexcept
{
cout << this << " move constructor from " << &another << endl;
this->str_ = another.str_;
another.str_ = nullptr;
}
移动构造必须加上noexcept,否则vector不会使用移动构造,依然使用拷贝构造。
A a;
vector<A> va(3,a);
cout<<"============================"<<endl;
va.push_back(a);
当A(A &&another)
去掉noexcept后,使用的依然是拷贝构造。
以上截取自《C++性能优化指南》P111。
以上截取自《C++ primer(第五版)》P474。