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;
}
View Code

 

參考侯捷<<STL源碼剖析>>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM