前言

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

最好阅读了C++11 move带来的高效

引入

这里我借上一篇C++11 move带来的高效中的CMyString类用一下,代码如下

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=(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;}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;}    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;        }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;     }
private:char* m_pStr;int m_nLen;
};

举一个栗子:我们内部有一个CoreFun函数需要一个CMyString对象,CoreFun函数我们不想暴露给客户,所以我们再封装一个ICoreFun来调用CoreFun,代码如下:

void CoreFun(CMyString t)
{cout << "CoreFun" << endl;
}void ICoreFun(CMyString t)
{cout << "ICoreFun" << endl;CoreFun(t);

很显然,ICoreFun函数只是起了一个转发的作用

测试一下:

int _tmain(int argc, _TCHAR* argv[])
{   CMyString lvalue("hello this is the lvalue");ICoreFun(lvalue);system("pause");
}

执行效果如下:

很显然,中间的一次拷贝构造函数是多余的,我们可以通过引用来优化掉:

void ICoreFun(CMyString& t)
{cout << "ICoreFun" << endl;CoreFun(t);
}

进阶

上面我们的ICoreFun函数的参数是一个左值引用,如果我需要传递一个右值进来的时候怎么办呢?例如ICoreFun(CMyString("hello this is the rvalue"));

(注:VS上自定义类对象右值也可以绑定到左值引用上)

难道要再写一个ICoreFun(CMyString&&)的接口吗?有没有什么办法

当我ICoreFun(lvalue);的时候ICoreFun的函数原型是ICoreFun(CMyString&)

当我ICoreFun(CMyString("hello this is the rvalue"));的时候ICoreFun的函数原型是ICoreFun(CMyString&&)

模板是一个很好的选择,如下的模板可以解决这个问题:

template <typename T>
void CoreFun(T t)
{cout << "CoreFun" << endl;
}template <typename T>
void ICoreFun(T&& t)
{cout << "ICoreFun" << endl;CoreFun(t);
}

想要理解如上模板的函数中T&&如何实现我们上面的需求的,需要知道C11引入的 “引用折叠规则” ( reference collapsing rules ),有如下两条:

对右值引用的两个规则中的第一个也同样影响了旧式的左值引用。回想在pre-11 C++时,对一个引用取引用是是不被允许的,比如A& &会导致一个编译器错误。相比之下,在C++11中,引入了如下所示的引用折叠规则(reference collapsing rules):

1. A& &变成A&
2. A& &&变成A&
3. A&& &变成A&
4. A&& &&变成A&&
第二条,有一个对应于函数模板中模板参数的特殊的演绎规则,当其模板参数为右值引用类型的时候:

template<typename T>
void foo(T&&);
下面,这些规则会生效:

当foo被一个类型为A的左值调用时,T会被转化为A&,因此根据上面的引用折叠规则,这个参数类型实际上会变成A&。
当foo被一个类型为A的右值调用是,T会被转化成A,因此这个参数类型实际上会变成A&&。

现在我们根据上面的规则来推到一下,T&&是怎么实现

当我ICoreFun(lvalue);的时候ICoreFun的函数原型是ICoreFun(CMyString&)

当我ICoreFun(CMyString("hello this is the rvalue"));的时候ICoreFun的函数原型是ICoreFun(CMyString&&)的:

运行程序看一下效果:

int _tmain(int argc, _TCHAR* argv[])
{   CMyString lvalue("hello this is the lvalue");ICoreFun(lvalue);cout << endl;ICoreFun(CMyString("hello this is the rvalue"));system("pause");
}

这里可以思考了,ICoreFun参数是右值的时候,拷贝构造函数是可以用 右值类型的拷贝构造函数优化的,回顾上一篇move带来的高效中,我们掌握了一种优化方式:对于使用临时对象来构造另一个对象的时候,完全可以通过右值类型的拷贝构造函数中的高效交换来实现,而不是调用拷贝构造函数重新进行new delete操作,因为临时对象马上就要销毁了,所以我们可以只是使用指针的交换来实现了构造的效果。

但是虽然ICoreFun(CMyString("hello this is the rvalue")); 转换为了ICoreFun(CMyString&& t),但是,t是一个左值,所以CoreFun(t);的时候调用的是拷贝构造函数来构造t,

t是一个左值都能理解吧,就和

int&& r = 8;

8是一个右值,r的类型是右值引用,r本身是一个左值,是一个道理的

全都是因为增加了一层转发,CMyString("hello this is the rvalue")从右值变为了左值

forward

有没有办法,在上面的情况中,在CMyString&& t 传递给CoreFun(t)的时候,CoreFun(t)把t当作左值来处理呢?也就在t是CMyString&&类型的时候把t转换为右值,在t是CMyString& t类型的时候不转换。forward就是这个作用。

forward作用:获取参数的原有类型(不太形象,具体看总结中的定义去理解下)

代码修改如下:

template <typename T>
void CoreFun(T t)
{cout << "CoreFun" << endl;
}template <typename T>
void ICoreFun(T&& t)
{cout << "ICoreFun" << endl;CoreFun(std::forward<T>(t));
}

运行结果:


转发左值就调用拷贝构造函数,抓发右值就调用右值类型的拷贝构造函数,完美。

总结

std::forward argument: Returns an rvalue reference to arg if arg is not an lvalue reference; If arg is an lvalue reference, the function returns arg without modifying its type.

std::forward:This is a helper function to allow perfect forwarding of arguments taken as rvalue references to deduced types, preserving any potential move semantics involved.

std::forward<T>(u)有两个参数:T 与 u。当T为左值引用类型时,u将被转换为T类型的左值,否则u将被转换为T类型右值。如此定义std::forward是为了在使用右值引用参数的函数模板中解决参数的完美转发问题。

std::move是无条件的转为右值引用,而std::forward是有条件的转为右值引用,更准确的说叫做Perfect forwarding(完美转发),而std::forward里面蕴含着的条件则是Reference Collapsing(引用折叠)。

std::move不move任何东西。std::forward也不转发任何东西。在运行时,他们什么都不做。不产生可执行代码,一个比特的代码也不产生。

std::move和std::forward只是执行转换的函数(确切的说应该是函数模板)。std::move无条件的将它的参数转换成一个右值,而std::forward当特定的条件满足时,才会执行它的转换。

std::move表现为无条件的右值转换,就其本身而已,它不会移动任何东西。 std::forward仅当参数被右值绑定时,才会把参数转换为右值。 std::move和std::forward在运行时不做任何事情。

C++11 forward完美转发相关推荐

  1. [C++11]forward完美转发

    // 函数原型 template <class T> T&& forward (typename remove_reference<T>::type& ...

  2. forward完美转发

    forward完美转发 std::forward是一个标准模板函数,它用于实现完美转发,即将输入的参数原封不动地传递给另一个函数,保持其左值或右值的属性. std::forward的作用是根据模板参数 ...

  3. C++11新特性之 std::forward(完美转发)

    上篇博客对右值.右值引用都做了简要介绍. 我们也要时刻清醒,有时候右值会转为左值,左值会转为右值. (也许"转换"二字用的不是很准确) 如果我们要避免这种转换呢? 我们需要一种方法 ...

  4. C++ std::move/std::forward/完美转发

    右值引用相关的几个函数:std::move, std::forward 和 成员的 emplace_back; 通过这些函数我们可以避免不必要的拷贝,提高程序性能. move 是将 对象的状态 或者 ...

  5. std:forward 完美转发

    概述:     // TEMPLATE CLASS identity template<class _Ty>     struct identity     {    // map _Ty ...

  6. forward在委托机制中的应用——完美转发

    forward在委托机制中的应用--完美转发 标签: forward完美转发委托机制 2017-02-07 21:19 63人阅读 评论(0) 收藏 举报  分类: C++(25)  版权声明:本文为 ...

  7. 可变参数模板、右值引用带来的移动语义完美转发、lambda表达式的理解

    可变参数模板 可变参数模板对参数进行了高度泛化,可以表示任意数目.任意类型的参数: 语法为:在class或者typename后面带上省略号. Template<class ... T> v ...

  8. C++左值与右值,移动与完美转发

    左值与右值 判别: 左值:用来存储数据的变量,有实际的内存地址,表达式结束后任然存在. 右值:匿名的临时变量,表达式结束时被销毁,不能存放数据,可以被修改或者不修改:字面常量也是右值. int x = ...

  9. C++11:forward及完美转发

    简介 一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值了,并不是原来的类型. 比如: template <typename T> void forwardVa ...

  10. 移动语义(move semantic)和完美转发(perfect forward)

    完整原文链接:https://codinfox.github.io/dev/2014/06/03/move-semantic-perfect-forward/ 移动语义(move semantic) ...

最新文章

  1. BI推荐8款优秀的app
  2. word count in latex, relatively accurate
  3. 为什么做Web开发要选择PHP
  4. 多态部分作业 2.编写2个接口:InterfaceA和InterfaceB;在接口InterfaceA中有个方法void 输出大小写字母表
  5. 什么是真正的APM?
  6. shell如何解决mysql交互式_shell脚本与mysql交互方法汇总
  7. [JSConf EU 2018] 大脑控制 Javascript
  8. MasterPage 小谈
  9. python导入数据库的数据怎么在qt界面里刷新_Python中使用pyqtgraph库实现数据可视化之逐点刷新波形图...
  10. 降本增效促提升---豪越创新企业后勤管理模式
  11. 证明:凸多边形裁剪一次最多能够新增一个凸多边形
  12. 股票做空机构-浑水公司
  13. 先马后看!详解线性回归、朴素贝叶斯、随机森林在R和Python中的实现应用!(附代码)...
  14. 数据挖据---机器学习平台之H2O架构/接口/实践
  15. python 常见算法题
  16. 微信小程序-如何处理时间戳
  17. win10将HTML动态做桌面壁纸,用win10自带工具,win10专业版简易制作动态壁纸教程...
  18. 1. 计算机网络概述
  19. h5获取当前浏览器ip和城市名称
  20. WTD测试框架(一)框架功能模块

热门文章

  1. java生成唯一的五位字符串_java唯一字符串ID生成方案详解
  2. 一种简单的生成伪随机数的方法(翻译)
  3. Codeforces Round #521 (Div. 3) E - Thematic Contests (二分 + STL)
  4. Grown Up Digital: How the Net Generation is Changing Your World
  5. Gentoo Linux 快速安装记录
  6. 服务器c盘怎么删除临时文件,删除临时文件夹中临时文件的方法
  7. Ps 初学者教程:如何对多张照片进行美化处理?
  8. 基于锁的并发数据结构:如何给数据结构加锁?
  9. [安卓手机安装Apk ] 安卓手机通过数据线在电脑下载本地的Apk应用
  10. craig gentry_为Craig投票!