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源码剖析>>

posted @ 2017-10-19 01:05  WangZijian  阅读(2698)  评论(0编辑  收藏  举报