C++11 move带来的高效

来源:互联网 发布:土耳其 知乎 编辑:程序博客网 时间:2024/06/05 02:00

前言

继续阅读之前,你最好了解了左值,右值,左值引用,右值引用等概念

引入

我由浅入深的引入move,先来看一个例子:
自己实现一个string类CMyString(简单实现了几个需要的函数),相信很多c++程序员面试的时候都会遇到,代码如下
//#include "stdafx.h"#include <iostream>using namespace std;class CMyString{public:CMyString(char* pStr): m_pStr(NULL), m_nLen(0){if (NULL != pStr){m_nLen = strlen(pStr);m_pStr = new char[m_nLen + 1];memcpy(m_pStr, pStr, m_nLen);m_pStr[m_nLen] = 0;cout << "一般构造函数 str=" << m_pStr << endl;}}CMyString(const CMyString& o): m_pStr(NULL), m_nLen(0){if (NULL != o.m_pStr){m_nLen = o.m_nLen;m_pStr = new char[m_nLen + 1];memcpy(m_pStr, o.m_pStr, m_nLen);m_pStr[m_nLen] = 0;cout << "拷贝构造函数 str=" << m_pStr << endl;}}const CMyString& operator=(const CMyString& o){if (this != &o){if (NULL != m_pStr){delete[] m_pStr;m_pStr = NULL;}m_nLen = o.m_nLen;if (NULL != o.m_pStr){m_pStr = new char[m_nLen + 1];memcpy(m_pStr, o.m_pStr, m_nLen);m_pStr[m_nLen] = 0;}cout << "重载赋值运算符 str=" << m_pStr << endl;}return *this;}~CMyString(){if (NULL != m_pStr){//cout << "析构函数 str=" << m_pStr << endl;delete m_pStr;}}char* GetData(){return m_pStr;}private:char* m_pStr;int m_nLen;};void swap(CMyString& str1, CMyString& str2){cout << "********************************************" << endl;CMyString tmp = str1;str1 = str2;str2 = tmp;cout << "********************************************" << endl;}int _tmain(int argc, _TCHAR* argv[]){CMyString str1("hello this is str1");CMyString str2("hello this is str2");swap(str1, str2);cout << "str1.GetData:" << str1.GetData() << endl;cout << "str2.GetData:" << str2.GetData() << endl;system("pause");}

上面代码中写了一个一般的swap函数(需要构建临时对象,一次拷贝构造,两次赋值)来交换两个CMyString的值,运行一下看看执行情况


是的,swap里面调用了一次拷贝构造,两次赋值操作,成功交换了两个对象的值,这个过程执行了很多new memcpy delete操作,在string内容很多的情况下,效率可想而知,我们都知道,swap里面是可以优化的,可以不创建临时对象,为此我们给CMyString函数增加一个swap成员函数来实现交换
void swap(CMyString& o){char* pStrTmp = o.m_pStr;int nLen = o.m_nLen;o.m_pStr = m_pStr;o.m_nLen = m_nLen;m_pStr = pStrTmp;m_nLen = nLen;}
交换的时候使用
str1.swap(str2);
看一下执行结果


通过直接交换内部数据指针的方式,成功交换了两个值,省去了很多无意义的new delete操作(实际std::string::swap函数实现原理类似)

进阶

再来看一个例子,假如有这样一种场景:我们构建一个CMyString对象str,使用它执行了一些操作,然后通过一个函数将str赋值为另一个值(这是一种很常见的场景)
CMyString GetCMyString(){return CMyString("hello this is the other one");}int _tmain(int argc, _TCHAR* argv[]){CMyString str("this is str");// use str do something// fun(str);str= GetCMyString();system("pause");        return 0;}
如上代码我们构建str并在fun中执行一系列操作之后使用GetCMyString给str赋予了新的值,执行效果如下:


如图所示,构造了other one,然后重载赋值运算符给str赋予了新的值,这里我们可以思考,能不能像上一个例子一样,通过交换指针的方式,优化掉重载赋值运算符函数中
delete new操作,因为other one是一个临时变量,return 之后立马就销毁了,没有其他的作用,所以我可以将str和它交换指针,这样str有了新值,other one析构销毁了原先str中的内容,也就是说因为临时对象马上就要销毁了,所以我们可以只是使用指针的交换来实现了构造的效果你可能会这样做,直接将重载赋值运算符函数改为如下:
const CMyString& operator=(CMyString& o){char* pStrTmp = o.m_pStr;int nLen = o.m_nLen;o.m_pStr = m_pStr;o.m_nLen = m_nLen;m_pStr = pStrTmp;m_nLen = nLen;return *this;}
执行效果如下:

虽然优化掉了重载赋值运算符函数中delete new操作,而且在本例中运行正常,但显然这是不可行的,本例中other one是一个临时变量,被返回后立马就销毁了。
但是如果是如下代码,肯定就出问题了
CMyString str("this is str");CMyString strImp("this is strimp");str = strImp;cout << "strImp.GetData:" << strImp.GetData() << endl;system("pause");
我本想将strImp赋值给str,但却修改了strImp中的内容,这不是我们期望的结果,那有没有这样一种方式:
如果是other one这种临时变量,我用交换指针的方式实现重载赋值运算符函数
如果是strImp这种长生命周期的普通变量,我执行原来的方式实现重载赋值运算符函数

c++11之后有了右值引用,我们可以实现这个思路,我们可以发现other one 是右值,strImp是左值,我们根据这个特性,可以增加一个参数为右值引用的重载赋值运算符函数,
如下所示:
const CMyString& operator=(CMyString&& o){char* pStrTmp = o.m_pStr;int nLen = o.m_nLen;o.m_pStr = m_pStr;o.m_nLen = m_nLen;m_pStr = pStrTmp;m_nLen = nLen;cout << "右值引用类型 重载赋值运算符 str=" << m_pStr << endl;return *this;}
测试一下:
int _tmain(int argc, _TCHAR* argv[]){CMyString str("this is str");CMyString strImp("this is strimp");// use str do something// fun(str);str= GetCMyString();cout << "str.GetData:" << str.GetData() << endl;str = strImp;cout << "strImp.GetData:" << strImp.GetData() << endl;system("pause");return 0;}


other one赋值调用的是右值引用类型的重载赋值运算符函数
strImp赋值调用的是普通的重载赋值运算符函数

有了右值引用类型 重载赋值运算符函数,同理可以引出右值引用类型 拷贝构造函数,实现如下:
CMyString(CMyString&& o): m_pStr(NULL), m_nLen(0){char* pStrTmp = o.m_pStr;int nLen = o.m_nLen;o.m_pStr = m_pStr;o.m_nLen = m_nLen;m_pStr = pStrTmp;m_nLen = nLen;cout << "右值引用类型 拷贝构造函数 str=" << m_pStr << endl;}

move

此时我们回顾一开始我们实现的普通swap函数和swap成员函数
void swap(CMyString& str1, CMyString& str2){cout << "********************************************" << endl;CMyString tmp = str1;str1 = str2;str2 = tmp;cout << "********************************************" << endl;}
void swap(CMyString& o){char* pStrTmp = o.m_pStr;int nLen = o.m_nLen;o.m_pStr = m_pStr;o.m_nLen = m_nLen;m_pStr = pStrTmp;m_nLen = nLen;}
之前我们发现,普通swap函数中的tmp对象带来了多余的new delete操作,我们使用swap成员函数优化掉了,如果不用swap成员函数来进行优化,有办法吗?
分析一下,普通swap函数中的三行代码执行了哪三个函数?  一次拷贝构造,两次赋值
结合进阶中的右值引用类型 重载赋值运算符函数和右值引用类型 拷贝构造函数来分析:
只要在执行拷贝构造函数的时候指定去执行右值引用类型 拷贝构造函数
执行赋值的时候去执行右值引用类型 重载赋值运算符函数,就可以实现,但是右值引用类型重载赋值运算符函数和右值引用类型 拷贝构造函数只有在
参数是右值的时候才会被调用,而swap函数中str1, tmp, str2都是左值

有没有办法把等号右侧的参数转换它们为右值呢?move出场了
move的作用:它接受一个参数,然后返回一个该参数对应的右值引用
swap改造如下:
void swap(CMyString& str1, CMyString& str2){cout << "********************************************" << endl;CMyString tmp = std::move(str1);str1 = std::move(str2);str2 = std::move(tmp);cout << "********************************************" << endl;}
测试一下:
int _tmain(int argc, _TCHAR* argv[]){CMyString str1("hello this is str1");CMyString str2("hello this is str2");swap(str1, str2);cout << "str1.GetData" << str1.GetData() << endl;cout << "str2.GetData" << str2.GetData() << endl;system("pause");}


总结

我们实现自己的类的时候,要有意识的实现右值引用类型的拷贝构造函数和右值引用类型的重载赋值运算符函数,
这样在交换的就可以使用move语义实现高效交换了(stl自带的类已经实现了)。









原创粉丝点击