第16课 右值引用(3)_std::forward与完美转发
1. std::forward原型
template <typename T>
T&& forward(typename std::remove_reference<T>::type& param) //左值引用版本
{
return static_cast<T&&>(param);
} template <typename T>
T&& forward(typename std::remove_reference<T>::type&& param) //右值引用版本
{
//param被右值初始化时,T应为右值引用类型,如果T被绑定为左值引用则报错。
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type"); return static_cast<T&&>(param);
} //其中remove_reference的实现如下
//1. 特化版本(一般的类)
template <typename T>
struct remove_reference
{
typedef T type;
}; //2. 左值引用版本
template <typename T>
struct remove_reference<T&>
{
typedef T type;
}; //3. 右值引用版本
template <typename T>
struct remove_reference<T&&>
{
typedef T type;
};
2. 完美转发(Perfect Forwarding)
(1)完美转发:是指在函数模板中,完全依照模板的参数类型(即保持实参的左值、右值特性),将实参传递给函数模板中调用的另外一个函数。
(2)原理分析
class Widget{};
//完美转发
template<typename T>
void func(T&& fparam) //fparam是个Universal引用
{
doSomething(std::forward<T>(fparam));
}
//1. 假设传入func是一个左值的Widget对象, T被推导为Widget&,则forward如下:
Widget& && forward(typename std::remove_reference<Widget&>::type& param)
{
return static_cast<Widget& &&>(param);
}
//==>引用折叠折后
Widget& forward(Widget& param)
{
return static_cast<Widget&>(param);
}
//2. 假设传入func是一个右值的Widget对象, T被推导为Wiget,则forward如下:
Widget&& forward(typename std::remove_reference<Widget>::type& param)
{
return static_cast<Widget&&>(param);
}
(3)std::forward和std::move的联系和区别
①std::move是无条件转换,不管它的参数是左值还是右值,都会被强制转换成右值。就其本身而言,它没有move任何东西。
②std::forward是有条件转换。只有在它的参数绑定到一个右值时,它才转换它的参数到一个右值。当参数绑定到左值时,转换后仍为左值。
③对右值引用使用std::move,对universal引用则使用std::forward
④如果局部变量有资格进行RVO优化,不要把std::move或std::forward用在这些局部变量中
⑤std::move和std::forward在运行期都没有做任何事情。
【编程实验】不完美转发和完美转发
#include <iostream>
//#include <utility> //for std::forward
using namespace std; void print(const int& t)
{
cout <<"lvalue" << endl;
} void print(int&& t)
{
cout <<"rvalue" << endl;
} template<typename T>
void Test(T&& v) //v是Universal引用
{
//不完美转发
print(v); //v具有变量,本身是左值,调用print(int& t) //完美转发
print(std::forward<T>(v)); //按v被初始化时的类型转发(左值或右值) //强制将v转为右值
print(std::move(v)); //将v强制转为右值,调用print(int&& t)
} int main()
{
cout <<"========Test(1)========" << endl;
Test(); //传入右值 int x = ;
cout <<"========Test(x)========" << endl;
Test(x); //传入左值 cout <<"=====Test(std::forward<int>(1)===" << endl;
Test(std::forward<int>()); //T为int,以右值方式转发1
//Test(std::forward<int&>(1)); //T为int&,需转入左值 cout <<"=====Test(std::forward<int>(x))===" << endl;
Test(std::forward<int>(x)); //T为int,以右值方式转发x
cout <<"=====Test(std::forward<int&>(x))===" << endl;
Test(std::forward<int&>(x)); //T为int,以左值方式转发x return ;
}
/*输出结果
e:\Study\C++11\16>g++ -std=c++11 test2.cpp
e:\Study\C++11\16>a.exe
========Test(1)========
lvalue
rvalue
rvalue
========Test(x)========
lvalue
lvalue
rvalue
=====Test(std::forward<int>(1)===
lvalue
rvalue
rvalue
=====Test(std::forward<int>(x))===
lvalue
rvalue
rvalue
=====Test(std::forward<int&>(x))===
lvalue
lvalue
rvalue
*/
3.万能的函数包装器
(1)利用std::forward和可变参数模板实现
①可将带返回值、不带返回值、带参和不带参的函数委托万能的函数包装器执行。
②Args&&为Universal引用,因为这里的参数可能被左值或右值初始化。Funciont&&也为Universal引用,如被lambda表达式初始化。
③利用std::forward将参数正确地(保持参数的左、右值属性)转发给原函数
【编程实验】万能的函数包装器
#include <iostream>
using namespace std; //万能的函数包装器
//可将带返回值、不带返回值、带参和不带参的函数委托万能的函数包装器执行 //注意:Args&&表示Universal引用,因为这里的参数可能被左值或右值初始化
// Funciont&&也为Universal引用,如被lambda表达式初始化
template<typename Function, class...Args>
auto FuncWrapper(Function&& func, Args&& ...args)->decltype(func(std::forward<Args>(args)...))
{
return func(std::forward<Args>(args)...);
} void test0()
{
cout << "void test0()" << endl;
} int test1()
{
return ;
} int test2(int x)
{
return x;
} string test3(string s1, string s2)
{
return s1 + s2;
} int main()
{ FuncWrapper(test0); cout << "int test1(): ";
cout << FuncWrapper(test1) << endl; cout << "int test2(int x): " ;
cout << FuncWrapper(test2, ) << endl; cout << "string test3(string s1, string s2): ";
cout << FuncWrapper(test3, "aa", "bb") << endl; cout << "[](int x, int y){return x + y;}: ";
cout << FuncWrapper([](int x, int y){return x + y;}, , ) << endl; return ;
}
/*输出结果:
e:\Study\C++11\16>g++ -std=c++11 test3.cpp
e:\Study\C++11\16>a.exe
void test0()
int test1(): 1
int test2(int x): 1
string test3(string s1, string s2): aabb
[](int x, int y){return x + y}: 3
*/
(2)emplace_back减少内存拷贝和移动
①emplace_back的实现原理类似于“万能函数包装器”,将参数std::forward转发给元素类的构造函数。实现上,首先为该元素开辟内存空间,然后在这片空间中调用placement new进行初始化,这相当于“就地”(在元素所在内存空间)调用元素对象的构造函数。
②而push_back会先将参数转为相应的元素类型,这需要调用一次构造函数,再将这个临时对象拷贝构造给容器内的元素对象,所以共需要一次构造和一次拷贝构造。从效率上看不如emplace_back,因为后者只需要一次调用一次构造即可。
③一般传入emplace_back的是构造函数所对应的参数(也只有这样传参才能节省一次拷贝构造),所以要求对象有相应的构造函数,如果没有对应的构造函数,则只能用push_back,否则编译会报错。如emplace_back(int, int),则要求元素对象需要有带两个int型的构造函数。
【编程实验】emplace_back减少内存拷贝和移动
#include <iostream>
#include <vector> using namespace std; class Test
{
int m_a;
public:
static int m_count; Test(int a) : m_a(a)
{
cout <<"Test(int a)" << endl;
} Test(const Test& t) : m_a(t.m_a)
{
++m_count;
cout << "Test(const Test& t)" << endl;
} Test& operator=(const Test& t)
{
this->m_a = t.m_a;
return *this;
}
}; int Test::m_count = ; int main()
{
//创建10个值为1的元素
Test::m_count = ;
vector<Test> vec(, ); //首先将1转为Test(1),会调用1次Test(int a)。然后,利用Test(1)去拷贝构造10个元素,所以
//调用10次拷贝构造。
cout << "vec.capacity():" << vec.capacity() << ", "; //
cout << "vec.size():" << vec.size() << endl; //10,空间己满 Test::m_count = ;
vec.push_back(Test()); //由于capacity空间己满。首先调用Test(1),然后再push_back中再拷贝
//构造10个元素(而不是1个,为了效率),所以调用10次拷贝构造
cout << "vec.capacity():" << vec.capacity() << ", "; //
cout << "vec.size():" << vec.size() << endl; //11,空间未满 Test::m_count = ;
vec.push_back(); //先调用Test(1),然后调用1次拷贝构造
cout << "vec.capacity():" << vec.capacity() << ", "; //
cout << "vec.size():" << vec.size() << endl; //12,空间未满 Test::m_count = ;
vec.emplace_back(); //由于空间未满,直接在第12个元素位置调用placement new初始化那段空间
//所以就会调用构造函数,节省了调用拷贝构造的开销
cout << "vec.capacity():" << vec.capacity() << ", "; //
cout << "vec.size():" << vec.size() << endl; //13,空间未满 Test::m_count = ;
vec.emplace_back(Test()); //先调用Test(1),再调用拷贝构造(注意与vec.emplace_back(1)之间差异)
cout << "vec.capacity():" << vec.capacity() << ", "; //
cout << "vec.size():" << vec.size() << endl; //14,空间未满 return ;
}
/*输出结果
e:\Study\C++11\16>g++ -std=c++11 test4.cpp
e:\Study\C++11\16>a.exe
Test(int a)
... //中间省略了调用10次Test(const Test& t)
vec.capacity():10, vec.size():10
Test(int a)
... //中间省略了调用10次Test(const Test& t)
vec.capacity():20, vec.size():11
Test(int a)
Test(const Test& t)
vec.capacity():20, vec.size():12
Test(int a)
vec.capacity():20, vec.size():13
Test(int a)
Test(const Test& t)
vec.capacity():20, vec.size():14
*/
第16课 右值引用(3)_std::forward与完美转发的更多相关文章
- Effective Modern C++:05右值引用、移动语义和完美转发
移动语义使得编译器得以使用成本较低的移动操作,来代替成本较高的复制操作:完美转发使得人们可以撰写接收任意实参的函数模板,并将其转发到目标函数,目标函数会接收到与转发函数所接收到的完全相同的实参.右值引 ...
- [c++11]右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- [转][c++11]我理解的右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- 第15课 右值引用(2)_std::move和移动语义
1. std::move (1)std::move的原型 template<typename T> typename remove_reference<T>::type& ...
- C++右值引用浅析
一直想试着把自己理解和学习到的右值引用相关的技术细节整理并分享出来,希望能够对感兴趣的朋友提供帮助. 右值引用是C++11标准中新增的一个特性.右值引用允许程序员可以忽略逻辑上不需要的拷贝:而且还可以 ...
- 翻译「C++ Rvalue References Explained」C++右值引用详解 Part8:Perfect Forwarding(完美转发):解决方案
本文为第八部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.ht ...
- 详解C++右值引用
C++0x标准出来很长时间了,引入了很多牛逼的特性[1].其中一个便是右值引用,Thomas Becker的文章[2]很全面的介绍了这个特性,读后有如醍醐灌顶,翻译在此以便深入理解. 目录 概述 mo ...
- C++ 11的右值引用
目录 一.问题导入 二.右值和右值引用 2.1 左值(lvalue)和右值(rvalue) 2.2 左值引用和右值引用 总结 参考资料 C++11 引入了 std::move 语义.右值引用.移动构造 ...
- [转载] C++11中的右值引用
C++11中的右值引用 May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导 ...
随机推荐
- Web APi入门之Self-Host寄宿及路由原理 【转载】
前言 刚开始表面上感觉Web API内容似乎没什么,也就是返回JSON数据,事实上远非我所想,不去研究不知道,其中的水还是比较深,那又如何,一步一个脚印来学习都将迎刃而解. Self-Host 我们知 ...
- python 高阶函数学习, map、reduce
一个函数可以接收另一个函数作为参数,这样的函数叫做高阶函数. 函数map(): map()函数接收两个参数,一个是函数,一个是Iterable, map把函数作用于序列的每一个元素,并把结果作为Ite ...
- BTrace学习总结
一.简介: 在生产环境中经常遇到格式各样的问题,如OOM或者莫名其妙的进程死掉.一般情况下是通过修改程序,添加打印日志:然后重新发布程序来完成.然而,这不仅麻烦,而且带来很多不可控的因素.有没有一种方 ...
- mongodb之 非正常关闭启动报错处理
Mongodb如果非正常关闭,直接启动会报错.查看日志文件. 处理: 需要做的是删除mongod.lock和WiredTiger.lock这两个lock文件,然后执行--repair,这里的mongo ...
- vue-cli 3.x 使用
vue-cli 3.x 安装:npm install -g @vue/cli vue-cli 3.x 常用命令 vue-cli 3.x 常用命令 命令含义 vue --help vue-cli 3.x ...
- 高阶组件 Higher-order Components (HOC) 知识点
官方介绍地址:https://reactjs.org/docs/higher-order-components.html
- Linux crontab配置
crontab配置 1.命令功能 通过crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script脚本.时间间隔的单位可以是分钟.小时.日.月.周及以上的任意组合.这个 ...
- 简单说说 Java 的 JVM 内存结构
问:简单说说 Java 的 JVM 内存结构分为哪几个部分? 答:JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分,分别解释如下.虚拟机栈:线程私有的,每个方法在执行时会创建一个 ...
- Cordova+Angularjs+Ionic 混合开发入门讲解
作为一名学习Android开发的学生,对于移动开发的发展趋势颇为关注,大家都知道,现在原生的移动开发在企业上基本很少使用了,大部分企业为了降低成本,选择了webapp,hybrid(混合开发)这两种模 ...
- PHP中使用sleep函数实现定时任务实例分享
在某些程序中,有一些特殊的功能需要用到定时执行,如果熟悉Linux的朋友肯定会说这不是容易吗,直接来个计划任务crontab不久实现了吗?这的确是可以实现,但必须是提前知道具体的执行时间,然后才能写到 ...