技術在於交流、溝通,本文為博主原創文章轉載請注明出處並保持作品的完整性
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源碼剖析>>