STL标准库-Move对容器效率的影响
技术在于交流、沟通,本文为博主原创文章转载请注明出处并保持作品的完整性
C++11新增move()语法(我暂时交错右值引用),在前面我有一篇文章叫 C++11_右值引用 简单的介绍了右值引用类的实现,这节我主要介绍一下为什么move()会更高效.
这次主要以一个带右值引用的Person类,和vector做测试
首先我们先实现一个带右值引用的Person类
class Person { public: static size_t DCtor; //记录默认构造函数调用次数 static size_t Ctor; //记录构造函数调用次数 static size_t CCtor;//记录拷贝函数调用次数 static size_t CAsgn;//记录赋值拷贝调用次数 static size_t MCtor;//记录move 构造调用次数 static size_t MAsgn;//记录move 赋值调用次数 static size_t Dtor;//记录析构函数调用次数 private: int _age; char* _name; size_t _len; void _test_name(const char *s) { _name = new char[_len+1]; memcpy(_name, s, _len); _name[_len] = '\0'; } public: //default ctor Person(): _age(0) , _name(NULL), _len(0){ DCtor++;} Person(const int age, const char * p) : _age(age), _len(strlen(p)) { _test_name(p); Ctor++; } //dctor ~Person(){ if(_name){ delete _name; } Dtor++; } // copy ctor Person (const Person& p):_age(p._age),_len(p._len){ _test_name(p._name); CCtor++; } //copy assignment Person & operator=(const Person& p) { if (this != &p){ if(_name) delete _name; _len = p._len; _age = p._age; _test_name(p._name); } else{ cout<< "self Assignment. Nothing to do." <<endl; } CAsgn++; return *this; } // move cotr , wihth "noexcept" Person(Person&& p) noexcept :_age(p._age) , _name(p._name), _len(p._len){ MCtor++; p._age = 0; p._name = NULL;//必须为NULL 如果你把这里设为空 那么这个函数走完之后将调用析够函数 因为当前的Person类 和你将要析够的Person的_name指向同一部分 析构部分见析构函数 } // move assignment Person& operator=(Person&& p) noexcept { if (this != &p) { if(_name) delete _name; _age = p._age; _len = p._len; _name = p._name; p._age = 0; p._len = 0; p._name = NULL; } MAsgn++; return *this; } }; size_t Person::DCtor = 0; size_t Person::Ctor = 0; size_t Person::CCtor = 0; size_t Person::CAsgn = 0; size_t Person::MCtor = 0; size_t Person::MAsgn = 0; size_t Person::Dtor = 0;
我们先看正常的拷贝构造函数
Person (const Person& p):_age(p._age),_len(p._len){ _test_name(p._name); CCtor++; }
它是先申请一段新的内存,然后将传进参数咋赋值给新的内存,型似下图
我们在看move 构造函数
// move cotr , wihth "noexcept" Person(Person&& p) noexcept :_age(p._age) , _name(p._name), _len(p._len){ MCtor++; p._age = 0; p._name = NULL;//必须为NULL 如果你把这里设为空 那么这个函数走完之后将调用析够函数 因为当前的Person类 和你将要析够的Person的_name指向同一部分 析构部分见析构函数 }
它值复制了指针,没有在去申请内存,就是我们常说的浅拷贝,它只是将原来指向数据的指针打断,然后将复制的指针指向数据,型似下图
只拷贝指针,当然比拷贝数据要快上很多
现在来验证一下上面的结论
template<typename M, typename NM> void test_moveable(M c1, NM c2, long& value) { char buf[10]; typedef typename iterator_traits<typename M::iterator>::value_type MyPerson;//萃取出type clock_t timeStart = clock();//记录起始时间 for(long i=0; i<value; i++) { snprintf(buf,10,"%d",rand()); auto ite = c1.end(); c1.insert(ite,MyPerson(0,buf)); } cout << "Move Person" << endl; cout << "construction, milli-seconds: "<<(clock()-timeStart) << endl;//验证构造耗时 cout << "size()= " << c1.size() << endl;//验证测试基数 我这里用三百万做基数 output_Static_data(*c1.begin()); cout << "copy, milli-seconds: "<<(clock()-timeStart) << endl;//验证copy耗时 timeStart = clock();// M c11(c1); cout << "move copy, milli-seconds: "<<(clock()-timeStart) << endl;//验证move copy函数耗时 timeStart = clock(); M c12(std::move(c1)); cout << "move construction, milli-seconds: "<<(clock()-timeStart) << endl;//验证Move 构造耗时 timeStart = clock(); c11.swap(c12);//验证seap耗时 cout << "swap, milli-seconds: "<<(clock()-timeStart) << endl; }
从测试结果中我们可以看出 拷贝构造与move构造的耗时差距是巨大的
我们来看一下C++11 vector中的move()的使用,下面是vector<>中的拷贝构造函数的源码
/** * @brief %Vector copy constructor. * @param __x A %vector of identical element and allocator types. * * The newly-created %vector uses a copy of the allocation * object used by @a __x. All the elements of @a __x are copied, * but any extra memory in * @a __x (for fast expansion) will not be copied. */ vector(const vector& __x) : _Base(__x.size(), _Alloc_traits::_S_select_on_copy(__x._M_get_Tp_allocator())) { this->_M_impl._M_finish = std::__uninitialized_copy_a(__x.begin(), __x.end(), this->_M_impl._M_start, _M_get_Tp_allocator()); }
这里其实就是一个move的使用,这个拷只拷贝指针的函数(将__x.end()赋值给_M_finish,将__x.begin()赋值给_M_impl._M_start),只复制指针,当然效率会更高
vector中还有一处用到了move(),那就是vector的move 构造函数
/** * @brief %Vector move constructor. * @param __x A %vector of identical element and allocator types. * * The newly-created %vector contains the exact contents of @a __x. * The contents of @a __x are a valid, but unspecified %vector. */ vector(vector&& __x) noexcept : _Base(std::move(__x)) { }
调用
_Vector_base(_Vector_base&& __x) noexcept : _M_impl(std::move(__x._M_get_Tp_allocator())) { this->_M_impl._M_swap_data(__x._M_impl); }
调用
void _M_swap_data(_Vector_impl& __x) _GLIBCXX_NOEXCEPT { std::swap(_M_start, __x._M_start); std::swap(_M_finish, __x._M_finish); std::swap(_M_end_of_storage, __x._M_end_of_storage); }
vector的move 构造函数 只是将上面的三个指针做了交换,也同样告诉了我们swap()耗时为什么也是这么短.
总结
move 给我们带来了更高效的语法,但是不要忘了,move的实质是浅拷贝,编程中尤其要注意浅拷贝的使用,因为浅拷贝一旦操作不当,可能造成不可预估的错误(如一个变量被删除两次)
上面介绍move虽然做了特殊处理,但是被move处理后的变量,依然不能再使用.(例:如果你使用了这段代码M c12(std::move(c1)); 那么在这之后一定不要在出现 c1 这个变量)
测试代码如下
#include <iostream> #include <vector> #include <string.h>//strlen() #include <typeinfo>//typeid().name() #include <iterator> #include <ctime> using namespace std; class CNoMovePerson { public: static size_t DCtor; static size_t Ctor; static size_t CCtor; static size_t CAsgn; static size_t MCtor; static size_t MAsgn; static size_t Dtor; private: int _age; char* _name; size_t _len; void _test_name(const char *s) { _name = new char[_len+1]; memcpy(_name, s, _len); _name[_len] = '\0'; } public: //default ctor CNoMovePerson(): _age(0) , _name(NULL), _len(0){DCtor++;} CNoMovePerson(const int age, const char * p) : _age(age), _len(strlen(p)) { _test_name(p); Ctor++; } //dctor ~CNoMovePerson(){ if(_name){ delete _name; } Dtor++; } // copy ctor CNoMovePerson (const CNoMovePerson& p):_age(p._age),_len(p._len){ _test_name(p._name); CCtor++;} //copy assignment CNoMovePerson & operator=(const CNoMovePerson& p) { if (this != &p){ if(_name) delete _name; _len = p._len; _age = p._age; _test_name(p._name); } else{ cout<< "self Assignment. Nothing to do." <<endl; } CAsgn++; return *this; } }; size_t CNoMovePerson::DCtor = 0; size_t CNoMovePerson::Ctor = 0; size_t CNoMovePerson::CCtor = 0; size_t CNoMovePerson::CAsgn = 0; size_t CNoMovePerson::MCtor = 0; size_t CNoMovePerson::MAsgn = 0; size_t CNoMovePerson::Dtor = 0; class Person { public: static size_t DCtor; static size_t Ctor; static size_t CCtor; static size_t CAsgn; static size_t MCtor; static size_t MAsgn; static size_t Dtor; private: int _age; char* _name; size_t _len; void _test_name(const char *s) { _name = new char[_len+1]; memcpy(_name, s, _len); _name[_len] = '\0'; } public: //default ctor Person(): _age(0) , _name(NULL), _len(0){ DCtor++;} Person(const int age, const char * p) : _age(age), _len(strlen(p)) { _test_name(p); Ctor++; } //dctor ~Person(){ if(_name){ delete _name; } Dtor++; } // copy ctor Person (const Person& p):_age(p._age),_len(p._len){ _test_name(p._name); CCtor++; } //copy assignment Person & operator=(const Person& p) { if (this != &p){ if(_name) delete _name; _len = p._len; _age = p._age; _test_name(p._name); } else{ cout<< "self Assignment. Nothing to do." <<endl; } CAsgn++; return *this; } // move cotr , wihth "noexcept" Person(Person&& p) noexcept :_age(p._age) , _name(p._name), _len(p._len){ MCtor++; p._age = 0; p._name = NULL;//必须为NULL 如果你把这里设为空 那么这个函数走完之后将调用析够函数 因为当前的Person类 和你将要析够的Person的_name指向同一部分 析构部分见析构函数 } // move assignment Person& operator=(Person&& p) noexcept { if (this != &p) { if(_name) delete _name; _age = p._age; _len = p._len; _name = p._name; p._age = 0; p._len = 0; p._name = NULL; } MAsgn++; return *this; } }; size_t Person::DCtor = 0; size_t Person::Ctor = 0; size_t Person::CCtor = 0; size_t Person::CAsgn = 0; size_t Person::MCtor = 0; size_t Person::MAsgn = 0; size_t Person::Dtor = 0; template<typename T> void output_Static_data(const T& myPerson) { cout << typeid(myPerson).name() << "--" << endl; cout << "CCtor=" << T::CCtor <<endl << "MCtor=" << T::MCtor <<endl << "CAsgn=" << T::CAsgn <<endl << "MAsgn=" << T::MAsgn <<endl << "Dtor=" << T::Dtor <<endl << "Ctor=" << T::Ctor <<endl << "DCtor=" << T::DCtor <<endl << endl; } template<typename M, typename NM> void test_moveable(M c1, NM c2, long& value) { char buf[10]; typedef typename iterator_traits<typename M::iterator>::value_type MyPerson; clock_t timeStart = clock(); for(long i=0; i<value; i++) { snprintf(buf,10,"%d",rand()); auto ite = c1.end(); c1.insert(ite,MyPerson(0,buf)); } cout << "Move Person" << endl; cout << "construction, milli-seconds: "<<(clock()-timeStart) << endl; cout << "size()= " << c1.size() << endl; output_Static_data(*c1.begin()); timeStart = clock(); M c11(c1); cout << "copy, milli-seconds: "<<(clock()-timeStart) << endl; timeStart = clock(); M c12(std::move(c1)); cout << "move construction, milli-seconds: "<<(clock()-timeStart) << endl; timeStart = clock(); c11.swap(c12); cout << "swap, milli-seconds: "<<(clock()-timeStart) << endl; cout << "------------------------------" << endl; cout << "No Move Person" << endl; typedef typename iterator_traits<typename NM::iterator>::value_type MyPersonNoMove; timeStart = clock(); for(long i=0; i<value; i++) { snprintf(buf,10,"%d",rand()); auto ite = c2.end(); c2.insert(ite,MyPersonNoMove(0,buf)); } cout << "construction, milli-seconds: "<<(clock()-timeStart) << endl; cout << "size()= " << c2.size() << endl; output_Static_data(*c1.begin()); timeStart = clock(); NM c22(c2); cout << "move copy, milli-seconds: "<<(clock()-timeStart) << endl; timeStart = clock(); NM c222(std::move(c2)); cout << "move construction, milli-seconds: "<<(clock()-timeStart) << endl; timeStart = clock(); c22.swap(c222); cout << "swap, milli-seconds: "<<(clock()-timeStart) << endl; } long value = 3000000; int main() { test_moveable(vector<Person>(),vector<CNoMovePerson>(),value); return 0; }
参考侯捷<<STL源码剖析>>