本次要讲的是右值引用相关的几个函数: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();
int x = ;
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 ;
} 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
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++11改进我们的程序之move和完美转发的更多相关文章

  1. (原创)c++11改进我们的程序之垃圾回收

    c#和java中有自动垃圾回收机制,.net运行时和java虚拟机可以管理分配的堆内存,在对象失去引用时自动回收,因此在c#和jva中, 内存管理不是大问题.c++语言没有垃圾回收机制,必须自己去释放 ...

  2. (原创)C++11改进我们的程序之简化我们的程序(八)

    本次要讲的是如何通过泛型函数来简化我们的程序. 泛型函数除了之前介绍的一些优点外还有两个重要的优点 1.消除重复逻辑,提高程序的内聚性和健壮性 泛型函数在某种程度上用来弥补泛型类型的不足.通过泛型类型 ...

  3. C++11改进我们的程序之简化我们的程序1

    C++11改进我们的程序之简化我们的程序(一) C++11在很多方面可以简化我们的程序开发,我会在“简化我们的程序”这一系列的博文中一一讲到,敬请关注.这次要讲的是:C++11如何通过获取函数模板的返 ...

  4. c++11 标准库函数 std::move 和 完美转发 std::forward

    c++11 标准库函数 std::move 和 完美转发 std::forward #define _CRT_SECURE_NO_WARNINGS #include <iostream> ...

  5. (原创)c++11改进我们的模式之改进代理模式,实现通用的AOP框架

    c++11 boost技术交流群:296561497,欢迎大家来交流技术. 本次要讲的时候如何改进代理模式,具体来说是动态代理模式,动态代理模式一般实现AOP框架,不懂AOP的童鞋看这里.我前面的博文 ...

  6. (原创)c++11改进我们的模式之改进命令模式

    模式虽然精妙,却难完美,比如观察者模式中观察者生命周期的问题:比如访问者模式中循环依赖的问题等等:其它很多模式也存在这样那样的一些不足之处,如使用场景受限.实现复杂.不够简洁.不够通用等.但我觉得不足 ...

  7. (原创)c++11改进我们的模式之改进访问者模式

    本次讲c++11改进我们的模式之改进访问者模式 访问者模式是GOF23个设计模式中比较复杂的模式之一,但是它的功能也很强大,非常适合稳定的继承层次中对象的访问,可以在不修改被访问对象的情况下,动态添加 ...

  8. [c++11]右值引用、移动语义和完美转发

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

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

    本次主要讲c++11中的右值引用,后面还会讲到右值引用如何结合std::move优化我们的程序. c++11增加了一个新的类型,称作右值引用(R-value reference),标记为T & ...

随机推荐

  1. springboot+mybatis+maven角色权限框架

    发布时间:2018-10-24   技术:springboot,mybatis,maven,shiro   概述 Springboot作为基础框架,使用mybatis作为持久层框架 使用官方推荐的th ...

  2. 有关于一次windows权限方面的一次学习

    由于最近需要使用windows的Local Group Policy的API,重新梳理一些有关windows权限的只是,这样需要理解一些关键概念,这些概念之间的关联联系形成了一张网络图.必须理解才能真 ...

  3. 【TP3.2 + 其他任何PHP框架】编辑、删除、添加数据,返回原分页 (ajax+form两种方式提交均可以)

    1.目的1:在如下的一个页面中,p=2,比如我们删除数据id=13,通过ajax提交{id,p} 这2个参数,就可以了,页面返回json的url参数中原样带上p即可. 2.目的2: 步骤1:在如下页面 ...

  4. 【MATLAB】matlabR2010a与vs2010联合编译设置

    在matlab中编译C++程序,首先要配置编译器>> mex -setupPlease choose your compiler for building external interfa ...

  5. linux运维常见英文报错中文翻译(菜鸟必知)

    linux常见英文报错中文翻译(菜鸟必知) 1.command not found  命令没有找到 2.No such file or directory  没有这个文件或目录 3.Permissio ...

  6. [项目实施失败讨论Case] “凭心而论,在这家公司很敬业的工作了3年多,老板最后给我下的评语,大家都看看吧,千万别和我走同一条路!”(摘自csdn)

    [Case] “凭心而论,在这家公司很敬业的工作了3年多,老板最后给我下的评语,大家都看看吧,千万别和我走同一条路!”(摘自csdn) 原文:http://community.csdn.net/Exp ...

  7. SoapUI利用Groovy把外部数据加载到request中

    默认已经用Groovy把外部数据给读取出来了,关键是读取出来后,如何加载到request中去?这里提供了两种方法:1.该Groovy脚本的名称是"setUp" def num = ...

  8. 【webservice】使用命令wsimport构建WebService客户端

    wsimport命令介绍 在JDK的bin文件夹中,有一个wsimport.exe,这个工具依据wsdl文件生成相应的类文件,然后用这些类文件,就可以像调用本地的类一样调用WebService提供的方 ...

  9. 使用SQL Server发送邮件时遇到的诡异事件

    最近公司要实现一个邮件群发的功能,因此设计时就考虑用SQL Server的邮件发送功能直接推送邮件算了. 可是在实现的过程中,邮件内容中有一个表格的内容要展现,于是就编排了一个表格来实现. 具体实现如 ...

  10. 技术的正宗与野路子 c#, AOP动态代理实现动态权限控制(一) 探索基于.NET下实现一句话木马之asmx篇 asp.net core 系列 9 环境(Development、Staging 、Production)

    黄衫女子的武功似乎与周芷若乃是一路,飘忽灵动,变幻无方,但举手抬足之间却是正而不邪,如说周芷若形似鬼魅,那黄衫女子便是态拟神仙. 这段描写出自<倚天屠龙记>第三十八回. “九阴神抓”本是& ...