std::move是一个用于提示优化的函数,过去的c++98中,由于无法将作为右值的临时变量从左值当中区别出来,所以程序运行时有大量临时变量白白的创建后又立刻销毁,其中又尤其是返回字符串std::string的函数存在最大的浪费。

比如:

 std::string fileContent = “oldContent”;
s = readFileContent(fileName);

因为并不是所有情况下,C++编译器都能进行返回值优化,所以,向上面的例子中,往往会创建多个字符串。readFileContent如果没有内 部状态,那么,它的返回值多半是std::string(const std::string的做法不再被推荐了),而不是const std::string&。这是一个浪费,函数的返回值被拷贝到s中后,栈上的临时对象就被销毁了。

在C++11中,编码者可以主动提示编译器,readFileContent返回的对象是临时的,可以被挪作他用:std::move。

将上面的例子改成:

 std::string fileContent = “oldContent”;
s = std::move(readFileContent(fileName));

后,对象s在被赋值的时候,方法std::string::operator =(std::string&&)会被调用,符号&&告诉std::string类的编写者,传入的参数是一个临时对 象,可以挪用其数据,于是std::string::operator =(std::string&&)的实现代码中,会置空形参,同时将原本保存在中形参中的数据移动到自身。

不光是临时变量,只要是你认为不再需要的数据,都可以考虑用std::move移动。

比较有名的std::move用法是在swap中:

 template<typename T>
void swap(T& a, T& b)
{
T t(std::move(a)); // a为空,t占有a的初始数据
a = std::move(b); // b为空, a占有b的初始数据
b = std::move(t); // t为空,b占有a的初始数据
}

总之,std::move是为性能而生的,正式因为了有了这个主动报告废弃物的设施,所以C++11中的STL性能大幅提升,即使C++用户仍然按找旧有的方式来编码,仍然能因中新版STL等标准库的强化中收益。

std::forward是用于模板编程中的,如果不需要编写通用的模板类和函数,可能不怎么用的上它。

要认识它的作用,需要知道C++中的几条规则:(这里有篇挺好的文章:http://blog.csdn.net/zwvista/article/details/6848582,但似乎因标准的更新,其中的规则已不完全成立了)

1. 引用折叠规则:

X& + & => X&
X&& + & => X&
X& + && => X&
X&& + && => X&&

2. 对于模板函数中的形参声明T&&(这里的模板参数T,最终推演的结果可能不是一个纯类型,它可能还会带有引用/常量修饰符,如,T推演为const int时,实际形参为const int &&),会有如下规则:

如果调用函数时的实参为U&(这里的U可能有const/volatile修饰,但没有左/右引用修饰了),那么T推演为U&,显然根据上面的引用折叠规则,U& &&=>U&。

如果调用实参为U&&,虽然将T推导为U&&和U都能满足折叠规则(U&&
&&=> U&&且U
&&=>U&&),但标准规定,这里选择将T推演为U而非U&&。

总结一下第2条规则:当形参声明为T&&时,对于实参U&,T被推演为U&;当实参是U&&时,T被推演为U。当然,T和U具有相同的const/volatile属性。

3.这点很重要,也是上面zwvista的文章中没有提到的:形参T&&
t中的变量t,始终是左值引用,即使调用函数的实参是右值引用也不例外。可以这么理解,本来,左值和右值概念的本质区别就是,左值是用户显示声明或分配内
存的变量,能够直接用变量名访问,而右值主要是临时变量。当一个临时变量传入形参为T&&
t的模板函数时,T被推演为U,参数t所引用的临时变量因为开始能够被据名访问了,所以它变成了左值。这也就是std::forward存在的原因!当你
以为实参是右值所以t也应该是右值时,它跟你开了个玩笑,它是左值!如果你要进一步调用的函数会根据左右值引用性来进行不同操作,那么你在将t传给其他函
数时,应该先用std::forward恢复t的本来引用性,恢复的依据是模板参数T的推演结果。虽然t的右值引用行会退化,变成左值引用,但根据实参的
左右引用性不同,T会被分别推演为U&和U,这就是依据!因此传给std::forward的两个参数一个都不能
少:std::forward<T>(t)。

再来,讨论一下,一个模板函数如果要保留参数的左右值引用性,为什么应该声明为T&&:

如果声明函数f(T t):实参会直接进行值传递,失去了引用性。

如果声明函数f(T &t): 根据引用折叠法则,无论T是U&还是U&&,T&的折叠结果都只会是U&,即,这个声明不能用于匹配右值引用实参。

如果声明函数f(T &&t):
如果T为U&,T&&的结果是U&,可以匹配左值实参;如果T为U&&,T&&的结果
是U&&,可以匹配右值实参。又因为T的cv性和U相同,所以这种声明能够保留实参的类型信息。

先来看一组帮助类:

 template<typename T> struct TypeName { static const char *get(){ return "Type"; } };
template<typename T> struct TypeName<const T> { static const char *get(){ return "const Type"; } };
template<typename T> struct TypeName<T&> { static const char *get(){ return "Type&"; } };
template<typename T> struct TypeName<const T&> { static const char *get(){ return "const Type&"; } };
template<typename T> struct TypeName<T&&> { static const char *get(){ return "Type&&"; } };
template<typename T> struct TypeName<const T&&> { static const char *get(){ return "const Type&&"; } };

在模板函数内部将模板参数T传给TypeName,就可以访问T的类型字符串:TypeName<T>::get()。

再一个帮助函数,用于打印一个表达式的类型:

 template<typename T>
void printValType(T &&val)
{
cout << TypeName<T&&>::get() << endl;
}

注意3条规则在这个模板函数上的应用。规则1,解释了T&& val的声明足以保留实参的类型信息。规则2,说明了,当实参是string&时,T就是string&;当实参是const string&&时,T就是const string(而非const string&&)。规则3,强调,无论实参是string&还是string&&,形参val的类型都是 string&!

注意TypeName<T&&>的写法,因为T只能为U&或者U,显然T&&可以根据折叠法则还原为实参类型U&和U&&。

这里是常见的const/左右引用组合的情形:

 class A{}; // 测试类
A& lRefA() { static A a; return a;} // 左值
const A& clRefA() { static A a; return a;} // 常左值
A rRefA() { return A(); } // 右值
const A crRefA() { return A(); } // 常右值

测试一下上面的表达式类型:

 printValType(lRefA());
printValType(clRefA());
printValType(rRefA());
printValType(crRefA());

输出依次是: Type&,const Type&,Type&&,const Type&&。

现在正式来探讨std::forward的实现。

回顾一下使用std::forward的原因:由于声明为f(T&& t)的模板函数的形参t会失去右值引用性质,所以在将t传给更深层函数前,可能会需要回复t的正确引用行,当然,修改t的引用性办不到,但根据t返回另一 个引用还是可以的。恰好,上面的函数printValType是一个会根据实参类型不同,作出不同反映的函数,所以可以把它作为f的内层函数,来检测f有 没有正确的修正t的引用行。

 template<typename T>
void f(T &&a)
{
printValType(a);
} int main()
{
f(lRefA());
f(clRefA());
f(rRefA());
f(crRefA());
}

输出:Type&,const Type&,Type&,const Type&。

可见后两个输出错了,这正是前面规则3描述的,当实参是右值引用时,虽然T被推演为U,但是参数a退化成了左值引用。

直接应用std::forward:

 template<typename T>
void f(T &&a)
{
printValType(std::forward<T>(a));
}

输出:Type&,const Type&,Type&&,const Type&&。

输出正确了,这就是std::forward的作用啊。如果更深层的函数也需要完整的引用信息,如这里的printValType,那就应该在传递形参前先std::forward!

在编写自己的forward函数之前,先来尝试直接强制转化参数a:

 template<typename T>
void f(T &&a)
{
printValType((T&&)a);
}

输出:Type&,const Type&,Type&&,const Type&&。

正确!因为不管T被推演为U&还是U,只要T&&肯定能还原为U&和U&&。

考虑下自己的forward函数应该怎么写:

因为在forward的调用方中,形参已经丢失了右值引用信息,唯一的参考依据是T,要根据T还原为正确的参数,得T&&,因 此,强制转换和返回类型都是T&&了,当然,forward还必须被以forward<T>()的方式显示指定模板类型,这 样才能保证forward的模板参数T和上层函数f的T是相同类型。首先:

 template<typename T>
T&& forward(... a)
{
return (T&&)a;
}

调用方f一定得显示指定类型forward<T>。

形参怎么写?形参a的类型由T构成,而且forward的实参一定是左值(暂时不考虑forward(std::string())的使用方法),也就是说,无论T是U&还是U,形参a的类型一定都得是U&,才能和实参匹配,所以,结果是:

 template<typename T>
T&& forward(T& a)
{
return (T&&)a;
}

测试,输出:Type&,const Type&,Type&&,const Type&&。
正确!

再试下,如果f调用forward的时候,使用forward(a)的方式,没有显示指定模板类型会怎么样:

 template<typename T>
void f(T &&a)
{
printValType(forward(a));
}

输出:T&&,const Type&&,Type&&,const Type&&。

错了。分析下,因为实参始终是左值,所以forward的形参T& a中,T就被推演为U,因此(T&&)a也就是(U&&)a所以结果错误。

为了避免用户使用forward(a),因此应该禁用forward的自动模板参数推演功能!可以借助std::identity,另外,将(T&&)换成static_cast<T&&>,规范一下:

 template<typename T>
T&& forward(typename std::identity<T>::type& a)
{
return static_cast<T&&>(a);
}

上面讲的是针对T为U&或U,而实参始终为左值的情况,这是常见的情形;不过也有实参为右值的情况,还需要改进上面这个forward,但我这里就不写了。

这是我手里的gcc4.5.2的forward实现:

   /// forward (as per N2835)
/// Forward lvalues as rvalues.
template<typename _Tp>
inline typename enable_if<!is_lvalue_reference<_Tp>::value, _Tp&&>::type
forward(typename std::identity<_Tp>::type& __t)
{ return static_cast<_Tp&&>(__t); } /// Forward rvalues as rvalues.
template<typename _Tp>
inline typename enable_if<!is_lvalue_reference<_Tp>::value, _Tp&&>::type
forward(typename std::identity<_Tp>::type&& __t)
{ return static_cast<_Tp&&>(__t); } // Forward lvalues as lvalues.
template<typename _Tp>
inline typename enable_if<is_lvalue_reference<_Tp>::value, _Tp>::type
forward(typename std::identity<_Tp>::type __t)
{ return __t; } // Prevent forwarding rvalues as const lvalues.
template<typename _Tp>
inline typename enable_if<is_lvalue_reference<_Tp>::value, _Tp>::type
forward(typename std::remove_reference<_Tp>::type&& __t) = delete;

第1/3版本就相当于我之前的实现,而版本2/4是实参为右值的情况,至于后者这种取舍的原因,还得去自己研究下使用场合和文档了。

我手里的vc2010实现的forward和我之前的实现相同,显然还不够,不过vc2010本来对标准也就还支持得少...

关于C++11中的std::move和std::forward的更多相关文章

  1. C++11中std::move、std::forward、左右值引用、移动构造函数的测试

    关于C++11新特性之std::move.std::forward.左右值引用网上资料已经很多了,我主要针对测试性能做一个测试,梳理一下这些逻辑,首先,左值比较熟悉,右值就是临时变量,意味着使用一次就 ...

  2. C++11 std::move和std::forward

    下文先从C++11引入的几个规则,如引用折叠.右值引用的特殊类型推断规则.static_cast的扩展功能说起,然后通过例子解析std::move和std::forward的推导解析过程,说明std: ...

  3. item 23: 理解std::move和std::forward

    本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 根据std::move和std::forward不 ...

  4. [C/C++]关于C++11中的std::move和std::forward

    http://www.cnblogs.com/cbscan/archive/2012/01/10/2318482.html http://blog.csdn.net/fcryuuhou/article ...

  5. 透彻理解C++11新特性:右值引用、std::move、std::forward

    目录 浅拷贝.深拷贝 左值.右值 右值引用类型 强转右值 std::move 重新审视右值引用 右值引用类型和右值的关系 函数参数传递 函数返还值传递 万能引用 引用折叠 完美转发 std::forw ...

  6. std::move()和std::forward()

    std::move(t)负责将t的类型转换为右值引用,这种功能很有用,可以用在swap中,也可以用来解决完美转发. std::move()的源码如下 template<class _Ty> ...

  7. C++0x,std::move和std::forward解析

    1.std::move 1.1std::move是如何定义的 template<typename _Tp> constexpr typename std::remove_reference ...

  8. C++11中std::move的使用

    std::move is used to indicate that an object t may be "moved from", i.e. allowing the effi ...

  9. c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用

    为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #includ ...

随机推荐

  1. 以太坊虚拟机(EVM)

    转载链接:https://ethfans.org/posts/solidity-chapter1-introduciton-to-smart-contracts 概括总览: 以太坊虚拟机(EVM)是以 ...

  2. FPGA速度等级

    转自http://wenku.baidu.com/view/ea793deef8c75fbfc77db263.html?from=rec 最初接触speed grade这个概念时,很是为Altera的 ...

  3. WordPaster-HDwik5.0整合教程

    示例下载:http://yunpan.cn/Q9ufQGuFMK34r 1.上传WordPaster文件夹 2.上传upload.php文件,这个文件负责接收控件上传的图片,并保存到uploads文件 ...

  4. java web 大文件下载

    泽优大文件下载产品测试 泽优大文件下载控件down2,基于php开发环境测试. 开发环境:HBuilder 服务器:wamp64 数据库:mysql 可视化数据库编辑工具:Navicat Premiu ...

  5. Docker网络简介

    Docker允许通过外部访问容器或则容器互联的方式来提供网络服务. 外部访问容器 容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过-P或则-P参数来指定断开映射.当使用 -P 标记时, ...

  6. django基础操作

    web应用程序:可以通过web访问的应用程序 bs/cs架构 http协议 基于TCP/IP协议之上的应用层协议 基于请求-响应模式:客户端先发送请求,服务端再响应 无状态保存:http协议对于发送的 ...

  7. android-基础编程-TextView

    TextView是简单而又复杂的控件,简单是使用上,复杂是源代码研究.基础编程这里只讲是如何使用. TextView主要分为两种使用方法,一种是xml中不带span的textview,另外一种是Spa ...

  8. jmeter 4.0版本更新说明(个人做个记录)总版本更新合集

    版本4.0 摘要 新的和值得注意的 不兼容的变化 Bug修复 改进 非功能性变化 已知问题和解决方法 谢谢 新的和值得注意的 核心改进 JMeter现在支持JAVA 9. 提供新的边界提取器元件,提供 ...

  9. Alpha阶段敏捷冲刺(六)

    1.站立式会议 提供当天站立式会议照片一张 2.每个人的工作 (有work item 的ID),并将其记录在码云项目管理中: 昨天已完成的工作. 祁泽文:查找了单词统计的方法. 徐璐琳:通过" ...

  10. POJ1274 The Perfect Stall

    The Perfect Stall Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 25739   Accepted: 114 ...