zoukankan      html  css  js  c++  java
  • [转] c++11 move 和 forward

    [转自 https://www.cnblogs.com/qicosmos/p/3376241.html]

    【参考 https://www.cnblogs.com/boydfd/p/5182743.html

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


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

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

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

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

    最后一行有如下的操作:

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

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

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

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

    1 {
    2 std::list< std::string > tokens;//省略初始化...
    3 std::list< std::string > t = tokens;
    4 }
    5 std::list< std::string > tokens;
    6 std::list< std::string > t = std::move(tokens);

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

    完美转发

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

     1 template<typename T>
     2 void PrintT(T& t)  // & 是左值引用
     3 {
     4 cout << "lvaue" << endl;
     5 }
     6 
     7 template<typename T>
     8 void PrintT(T && t)  // && 是右值引用
     9 {
    10 cout << "rvalue" << endl;
    11 }
    12 
    13 template<typename T>
    14 void TestForward(T && v)
    15 {
    16 PrintT(v);
    17 PrintT(std::forward<T>(v));
    18 PrintT(std::move(v));
    19 }
    20 
    21 Test()
    22 {
    23 TestForward(1);
    24 int x = 1;
    25 TestForward(x);
    26 TestForward(std::forward<int>(x));
    27 }

     测试结果:


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

    • 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);

    万能的函数包装器

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

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

    再看看测试代码:

     1 void test0()
     2 {
     3 cout << "void" << endl;
     4 }
     5 
     6 int test1()
     7 {
     8 return 1;
     9 }
    10 
    11 int test2(int x)
    12 {
    13 return x;
    14 }
    15 
    16 string test3(string s1, string s2)
    17 {
    18 return s1 + s2;
    19 }
    20 
    21 test()
    22 {
    23 FuncWrapper(test0);  //没有返回值,打印1
    24 FuncWrapper(test1); //返回1
    25 FuncWrapper(test2, 1); //返回1
    26 FuncWrapper(test3, "aa", "bb"); //返回"aabb"
    27 }

    成员的emplace_back

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

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

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

  • 相关阅读:
    .net Core 使用AutoMapper
    文件批量生成IO流读写
    .net Core数据的幕等性
    .net core 拦截器的使用
    墙上你APP设计与实现
    H5 App实现热更新,不需要重新安装app
    支付宝支付接口的使用详细说明
    .net 数据源DataSet 转换成模型
    .net ajax跨域请求问题
    【系统之音】SystemUI篇(二)SysytemUI功能一览--草稿
  • 原文地址:https://www.cnblogs.com/yi-mu-xi/p/10001579.html
Copyright © 2011-2022 走看看