http://www.cnblogs.com/qicosmos/p/3376241.html

本次要讲的是右值引用相关的几个函数:std::move, std::forward和成员的emplace_back,通过这些函数我们可以避免不必要的拷贝,提高程序性能。move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。如图所示是深拷贝和move的区别。


  这种移动语义是很有用的,比如我们一个对象中有一些指针资源或者动态数组,在对象的赋值或者拷贝时就不需要拷贝这些资源了。在c++11之前我们的拷贝构造函数和赋值函数可能要这样定义:
假设一个A对象内部有一个资源m_ptr;

A& A::operator=(const A& rhs)
{
// 销毁m_ptr指向的资源
// 复制rhs.m_ptr所指的资源,并使m_ptr指向它
}

同样A的拷贝构造函数也是这样。假设我们这样来用A:

A foo(); // foo是一个返回值为X的函数
A a;
a = foo();

最后一行有如下的操作:

  • 销毁a所持有的资源
  • 复制foo返回的临时对象所拥有的资源
  • 销毁临时对象,释放其资源

  上面的过程是可行的,但是更有效率的办法是直接交换a和临时对象中的资源指针,然后让临时对象的析构函数去销毁a原来拥有的资源。换句话说,当赋值操作符的右边是右值的时候,我们希望赋值操作符被定义成下面这样:

A& A::operator=(const A&& rhs)
{
// 仅仅转移资源的所有者,将资源的拥有者改为被赋值者
}

  这就是所谓的move语义。再看一个例子,假设一个临时容器很大,赋值给另一个容器。

{
std::list< std::string > tokens;//省略初始化...
std::list< std::string > t = tokens;
}
std::list< std::string > tokens;
std::list< std::string > t = std::move(tokens);

  如果不用std::move,拷贝的代价很大,性能较低。使用move几乎没有任何代价,只是转换了资源的所有权。如果一个对象内部有较大的对内存或者动态数组时,很有必要写move语义的拷贝构造函数和赋值函数,避免无谓的深拷贝,以提高性能。

完美转发

  在上一篇的博文中我介绍了右值引用,右值引用类型是独立于值的,一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值了,并不是它原来的类型了。因此,我们需要一种方法能按照参数原来的类型转发到另一个函数,这种转发被称为完美转发。所谓完美转发(perfect forwarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。c++11中提供了这样的一个函数std::forward,它是为转发而生的,它会按照参数本来的类型来转发出去,不管参数类型是T&&这种未定的引用类型还是明确的左值引用或者右值引用。看看这个例子。

template<typename T>
void PrintT(T& t)
{
cout << "lvaue" << endl;
}template<typename T>
void PrintT(T && t)
{
cout << "rvalue" << endl;
}template<typename T>
void TestForward(T && v)
{
PrintT(v);
PrintT(std::forward<T>(v));
PrintT(std::move(v));
}Test()
{
TestForward(1);
int x = 1;
TestForward(x);
TestForward(std::forward<int>(x));
}

  测试结果:


  我们来分析一下测试结果:

  • TestForward(1);由于1是右值,所以未定的引用类型T && v被一个右值初始化后变成了一个右值引用,但是在TestForward函数体内部,调用PrintT(v);时,v又变成了一个左值,因为它这里已经变成了一个具名的变量,所以它是一个左值,因此第一个PrintT被调用,打印出"lvaue";PrintT(std::forward<T>(v));由于std::forward会按参数原来的类型转发,因此,这时它还是一个右值(这里已经发生了类型推导,所以这里的T&&不是一个未定的引用类型,关于这点可以参考我的上一篇讲右值引用的博文),所以会调用void PrintT(T &&t)函数。PrintT(std::move(v));是将v变成一个右值引用,虽然它本来也是右值引用,因此它和PrintT(std::forward<T>(v));的输出结果是一样的。
  • TestForward(x);未定的引用类型T && v被一个左值初始化后变成了一个左值引用,因此在调用PrintT(std::forward<T>(v));它会转发到void PrintT(T& t);

万能的函数包装器

  右值引用、完美转发再结合可变模板参数,我们可以写一个万能的函数包装器,它可以接收所有的函数,带返回值的、不带返回值的、带参数的和不带参数的函数都可以委托这个万能的函数包装器执行。看看这个万能的函数包装器。

template<class Function, class... Args>
inline auto FuncWrapper(Function && f, Args && ... args) -> decltype(f(std::forward<Args>(args)...))
{
//typedef decltype(f(std::forward<Args>(args)...)) ReturnType;
return f(std::forward<Args>(args)...);
//your code; you can use the above typedef.
}

再看看测试代码:

void test0()
{
cout << "void" << endl;
}int test1()
{
return 1;
}int test2(int x)
{
return x;
}string test3(string s1, string s2)
{
return s1 + s2;
}test()
{
FuncWrapper(test0);  //没有返回值,打印1
FuncWrapper(test1); //返回1
FuncWrapper(test2, 1); //返回1
FuncWrapper(test3, "aa", "bb"); //返回"aabb"
}

成员的emplace_back

  c++11中大部分容器都加了一个emplace_back成员函数,vector中它的定义是这样的:

template< class... Args >
void emplace_back( Args&&... args );

  这里的Args&&是一个未定的引用类型,因此它可以接收左值引用和右值引用,它的内部也是调用了std::forward实现完美转发的。因此如果我们需要往容器中添加右值、临时变量时,用emplace_back可以提高性能。

c++11 boost技术交流群:296561497,欢迎大家来交流技术。

一点梦想:尽自己一份力,让c++的世界变得更美好!

(原创)C++11改进我们的程序之move和完美转发相关推荐

  1. C++11 右值引用、移动语义、完美转发、万能引用

    C++11 右值引用.移动语义.完美转发.引用折叠.万能引用 转自:http://c.biancheng.net/ C++中的左值和右值 右值引用可以从字面意思上理解,指的是以引用传递(而非值传递)的 ...

  2. C++11右值引用、移动语义、完美转发详解

    c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...

  3. (原创)C++11改进我们的程序之右值引用

    http://www.cnblogs.com/qicosmos/p/3369940.html 本次主要讲c++11中的右值引用,后面还会讲到右值引用如何结合std::move优化我们的程序. c++1 ...

  4. 红心大战c语言程序设计教程课后答案,[原创]Windows 红心大战随机发牌程序分析...

    [调试逆向] [原创]Windows 红心大战随机发牌程序分析 2007-11-27 23:28 13191 [调试逆向] [原创]Windows 红心大战随机发牌程序分析 2007-11-27 23 ...

  5. [原创]软件测试过程改进的内容和注意事项

    [原创]软件测试过程改进的内容和注意事项 软件开发过程的质量决定了软件系统的质量,同样软件测试过程改进的质量决定了测试的质量和效率.其中,测试技术解决了测试采用的方法和技术问题,测试管理保证各项测试活 ...

  6. 无法安装驱动程序此计算机上不存在,11.2.4 “安装程序没有找到安装在此计算机上的硬盘驱动器”问题 (1)...

    11.2.4 "安装程序没有找到安装在此计算机上的硬盘驱动器"问题(1) 在安装操作系统的时候,安装程序提示"安装程序没有找到安装在此计算机上的硬盘驱动器--" ...

  7. 虚拟机安装程序没有找到安装在此计算机上的硬盘驱动器,11.2.4 “安装程序没有找到安装在此计算机上的硬盘驱动器”问题(3)...

    11.2.4 "安装程序没有找到安装在此计算机上的硬盘驱动器"问题(3) (9)安装程序启动后(大约等几秒),当屏幕下文出现"Press F6 if you need t ...

  8. c语言程序函数的结构,C语言课件:第11讲函数与程序结构

    <C语言课件:第11讲函数与程序结构>由会员分享,可在线阅读,更多相关<C语言课件:第11讲函数与程序结构(21页珍藏版)>请在人人文库网上搜索. 1.第11讲 函数与程序结构 ...

  9. 【2020年11月】编程语言 / 程序员工资排行榜

    数据来自:https://www.tiobe.com/tiobe-index/ 自TOIBE编程语言排行榜发布20年以来,C和Java语言一直占据排行榜第一.第二的位置,不过这一局势在本月发生了扭转, ...

最新文章

  1. 关于升级 xcode8
  2. Git remote 修改源
  3. GCD Game 博弈论-Nim-质因数应用-质因数个数预处理
  4. Mac安装brew包管理工具
  5. 完整的OTT直播点播系统都有哪些功能?
  6. linux 在线帮助,linux教程之在线帮助
  7. php memcached 队列,redis获取所有队列_memcached
  8. 人工计算机的相关信息,第三届计算机信息科学与人工智能国际学术会议(CISAI 2020)...
  9. OpenCV-PS扩散效果(毛玻璃)
  10. 计算机网络考研笔记(持续更新)
  11. B/S端界面控件DevExtreme Gantt控件——可轻松导出PDF
  12. 零基础学Python课后实战第五章
  13. ASA入门实验之NAT
  14. 每个程序员书柜必有的编程书籍
  15. 详细的辅助开发教程,从入门到精通
  16. 通过初始时间和流逝的分钟数计算终止时间
  17. 分布式存储与分布式计算
  18. 饥荒(steam)服务器解决网络问题差的一种简单方法
  19. 无法启动计算机的病毒是,电脑中毒无法启动 卡巴斯基可解决危难
  20. java web目录文件中.setting这个文件夹是什么意思

热门文章

  1. Spring入门第二十五课
  2. hdu--4902--线段树
  3. 调用WindowsAPI显示帮助提示
  4. 计算机科学研究生规划,2019计算机考研备考:计算机科学与技术研究方向及复习规划...
  5. ble连接过程建立_九点之蓝牙连接
  6. java ssh shell命令_java 通过ssh 执行命令
  7. mysql 10个日期,MySQL自学篇(10)——日期函数
  8. html5与css3都要学吗,前端要学css3吗?
  9. java多文件post请求_如何使用Java发出多部分/表单数据POST请求?
  10. 多个数字数组_七个问题帮助初学者深入理解Java数组