zoukankan      html  css  js  c++  java
  • 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] = '';
        }
    
    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] = '';
        }
    
    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] = '';
        }
    
    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源码剖析>>

  • 相关阅读:
    ACM的探索之Keen On Evrything But Triangle(我觉得可以很接近啦!!)
    ACM的探索之Just Skip The Problem
    ACM的探索之Everything Is Generated In Equal Probability(这真的是很有趣的话语丫!)
    心情散记
    识别有效的IP地址和掩码并进行分类统计
    网络流复习计划
    CF1076D Edge Deletion
    bzoj4563 HAOI2016放旗子
    BZOJ2152聪聪可可
    BZOJ3224普通平衡树
  • 原文地址:https://www.cnblogs.com/LearningTheLoad/p/7690052.html
Copyright © 2011-2022 走看看