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+];
memcpy(_name, s, _len);
_name[_len] = '\0';
} public:
//default ctor
Person(): _age() , _name(NULL), _len(){ 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 = ;
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 = ;
p._len = ;
p._name = NULL;
}
MAsgn++;
return *this;
}
}; size_t Person::DCtor = ;
size_t Person::Ctor = ;
size_t Person::CCtor = ;
size_t Person::CAsgn = ;
size_t Person::MCtor = ;
size_t Person::MAsgn = ;
size_t Person::Dtor = ;
我们先看正常的拷贝构造函数
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 = ;
p._name = NULL;//必须为NULL 如果你把这里设为空 那么这个函数走完之后将调用析够函数 因为当前的Person类 和你将要析够的Person的_name指向同一部分 析构部分见析构函数
}
它值复制了指针,没有在去申请内存,就是我们常说的浅拷贝,它只是将原来指向数据的指针打断,然后将复制的指针指向数据,型似下图

只拷贝指针,当然比拷贝数据要快上很多
现在来验证一下上面的结论
template<typename M, typename NM>
void test_moveable(M c1, NM c2, long& value)
{
char buf[]; typedef typename iterator_traits<typename M::iterator>::value_type MyPerson;//萃取出type clock_t timeStart = clock();//记录起始时间
for(long i=; i<value; i++)
{
snprintf(buf,,"%d",rand());
auto ite = c1.end();
c1.insert(ite,MyPerson(,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+];
memcpy(_name, s, _len);
_name[_len] = '\0';
} public:
//default ctor
CNoMovePerson(): _age() , _name(NULL), _len(){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 = ;
size_t CNoMovePerson::Ctor = ;
size_t CNoMovePerson::CCtor = ;
size_t CNoMovePerson::CAsgn = ;
size_t CNoMovePerson::MCtor = ;
size_t CNoMovePerson::MAsgn = ;
size_t CNoMovePerson::Dtor = ; 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+];
memcpy(_name, s, _len);
_name[_len] = '\0';
} public:
//default ctor
Person(): _age() , _name(NULL), _len(){ 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 = ;
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 = ;
p._len = ;
p._name = NULL;
}
MAsgn++;
return *this;
}
}; size_t Person::DCtor = ;
size_t Person::Ctor = ;
size_t Person::CCtor = ;
size_t Person::CAsgn = ;
size_t Person::MCtor = ;
size_t Person::MAsgn = ;
size_t Person::Dtor = ; 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[]; typedef typename iterator_traits<typename M::iterator>::value_type MyPerson; clock_t timeStart = clock();
for(long i=; i<value; i++)
{
snprintf(buf,,"%d",rand());
auto ite = c1.end();
c1.insert(ite,MyPerson(,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=; i<value; i++)
{
snprintf(buf,,"%d",rand());
auto ite = c2.end();
c2.insert(ite,MyPersonNoMove(,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 = ;
int main()
{
test_moveable(vector<Person>(),vector<CNoMovePerson>(),value);
return ;
}
参考侯捷<<STL源码剖析>>
STL标准库-Move对容器效率的影响的更多相关文章
- STL标准库中的容器
容器:顾名思义,我的理解就是把同一种数据类型括起来,作为一捆.如vector<int> ,vector就是个容器,里面全是一个个的int型数据. 容器包括三大块: 顺序型容器: (1)ve ...
- STL标准库-容器-set与multiset
技术在于交流.沟通,转载请注明出处并保持作品的完整性. set与multiset关联容器 结构如下 set是一种关联容器,key即value,value即key.它是自动排序,排序特点依据key se ...
- STL标准库-容器-deque
技术在于交流.沟通,本文为博主原创文章转载请注明出处并保持作品的完整性. deque双向开口可进可出的容器 我们知道连续内存的容器不能随意扩充,因为这样容易扩充别人那去 deque却可以,它创造了内存 ...
- STL标准库-容器-vector
技术在于交流.沟通,本文为博主原创文章转载请注明出处并保持作品的完整性. 向量容器vector是一个动态数组,内存连续,它是动态分配内存,且每次扩张的原来的二倍. 他的结构如下 一 定义 vector ...
- STL标准库-容器-set与map
STL标准库-容器-set与multiset C++的set https://www.cnblogs.com/LearningTheLoad/p/7456024.html STL标准库-容器-map和 ...
- STL标准库-算法-常用算法
技术在于交流.沟通,本文为博主原创文章转载请注明出处并保持作品的完整性 介绍11种STL标准库的算法,从这11种算法中总结一下算法的基本使用 1.accumulate() 累加 2.for_each( ...
- C++STL标准库学习笔记(五)set
前言: 在这个笔记中,我把大多数代码都加了注释,我的一些想法和注解用蓝色字体标记了出来,重点和需要关注的地方用红色字体标记了出来,这一篇后面主要都是我的记录了,为了防止大片蓝色字体出现,后面就不改蓝色 ...
- C++STL标准库学习笔记(三)multiset
C++STL标准库学习笔记(三)multiset STL中的平衡二叉树数据结构 前言: 在这个笔记中,我把大多数代码都加了注释,我的一些想法和注解用蓝色字体标记了出来,重点和需要关注的地方用红色字体标 ...
- C++STL标准库学习笔记(一)sort
前言: 近来在学习STL标准库,做一份笔记并整理好,方便自己梳理知识.以后查找,也方便他人学习,两全其美,快哉快哉! 这里我会以中国大学慕课上北京大学郭炜老师的<程序设计与算法(一)C语言程序设 ...
随机推荐
- BZOJ5293: [Bjoi2018]求和 树上差分
Description master 对树上的求和非常感兴趣.他生成了一棵有根树,并且希望多次询问这棵树上一段路径上所有节点深度的k 次方和,而且每次的k 可能是不同的.此处节点深度的定义是这个节点 ...
- Java课程设计—拿火柴小游戏
这是我的Java课程设计,做的是拿火柴小游戏.要求是人机互拿火柴,拿到最后一根火柴者赢.最主要的就是处理好多线程,其他的就是添加组件啥的,那个不难,我就给出以下多线程的代码吧. import java ...
- 2:JavaScript中的基本运算
今天说的是JavaScript中的数据基本运算 在上一节中已经说了关于JavaScript中的基本数据类型 那么数据有了 剩下来就是数据之间的运算 表达式-------预算符(赋值 比较 算数 逻辑 ...
- 《剑指offer》第十七题(打印1到最大的n位数)
// 面试题17:打印1到最大的n位数 // 题目:输入数字n,按顺序打印出从1最大的n位十进制数.比如输入3,则 // 打印出1.2.3一直到最大的3位数即999. #include <ios ...
- 安装gcc4.8.5
安装gcc4.8.51. g++ --version, 4.4.7不支持c++112. 升级gcc-c++, 下载gcc https://gcc.gnu.org/ 官网,镜像下载地址https: ...
- C#实现在应用程序间发送消息的方法示例
本文实例讲述了C#实现在应用程序间发送消息的方法.分享给大家供大家参考,具体如下: 首先建立两个C#应用程序项目. 第一个项目包含一个Windows Form(Form1),在Form1上有一个But ...
- Vue自动化工具(Vue-CLI)
一.组件的概念 1.概念 组件(Component)是自定义封装的功能.在前端开发过程中,经常出现多个网页的功能是重复的,而且很多不同的网站之间,也存在同样的功能. 而在网页中实现一个功能,需要使用h ...
- LeetCode 48. Rotate Image My Submissions Question (矩阵旋转)
题目大意:给一个矩阵,将其按顺时针旋转90°. 题目分析:通法是先将矩阵转置,然后再反转每一行,或者是先反转每一列,然后再将其转置.I just want to say"It's amazi ...
- 关于vue Unexpected identifier 问题
vue对于es6虽然自带babel转换 但是在index.html文件中并不会发生转换 因此在index.html中使用新的语法会导致低版本浏览器不识别代码因此报出Unexpected identif ...
- 决策树ID3算法实现
决策树的ID3算法基于信息增益来选择最优特征,于是自己实现了一把,直接上代码. """ CreateTime : 2019/3/3 22:19 Author : X Fi ...