zoukankan      html  css  js  c++  java
  • C++ std::move/std::forward/完美转发

    右值引用相关的几个函数: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 语义。在看一个例子,假设一个临时容器很大,赋值给另一个容器。

    1 {
    2     std::list<std::string> tokens;  //省略初始化....
    3     std::list<std::string> t = tokens;      
    4 }
    5 
    6 std::list<std::string> tokens; 
    7 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     std::cout << "lvalue"  << std::endl;
     5 }
     6 
     7 template<typename T>
     8 void PrintT(T&& t)
     9 {
    10     std::cout << "rvalue" << std::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 }

    测试结果:

    lvalue

    rvalue

    rvalue

    lvalue

    lvalue

    rvalue

    lvalue

    rvalue

    rvalue

    我们来分析测试结果:

    TestForward(1); 由于 1 是右值,所以未定的引用类型 T&& v 被一个右值初始化后变成了一个右值引用,但是在 TestForward 函数体内部,调用 PrintT(v); 时, v 又变成了一个左值,因为他这里已经变成了一个具名的变量,所以它是一个左值,因此 第一个 PrintT 被调用,打印出 "lvalue";

    PrintT(std::forward<T>(v)); 由于 std::froward 会按参数原来的类型转发, 因此,这时它还是一个右值(这里已经发生了类型推导,所以这里的 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>(argcs)...))
     3 {
     4     //typedef decltype(f(std::forward<Args>(args)...)) ReturnType;
     5     return f(std::forward<Args>(args)...);
     6     //your code; youcan use the above typedef.    
     7 }
     8 
     9 //测试代码
    10 void test0()
    11 {
    12     cout << "void" << endl;
    13 }
    14 
    15 int test1()
    16 {
    17     return 1;
    18 }
    19 
    20 int test2(int x)
    21 {
    22     return x;
    23 }
    24 
    25 string test3(string s1, string s2)
    26 {
    27     return s1+s2;
    28 }
    29 
    30 test()
    31 {
    32 FuncWrapper(test0);  //没有返回值,打印1
    33 FuncWrapper(test1);  //返回1
    34 FuncWrapper(test2, 1); //返回1
    35 FuncWrapper(test3, "aa", "bb");  //返回“aabb”
    36 }


    成员的emplace_back

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

    template<class...Args>

    void emplace_back(Args&&... args);

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

  • 相关阅读:
    内部类&匿名内部类
    Object:
    多 态★★★★★(面向对象特征之三)
    接 口:
    继 承(面向对象特征之二)
    封 装(面向对象特征之一)
    三:面向对象:★★★★★
    Codeforces 719 E. Sasha and Array (线段树+矩阵运算)
    uestc oj 1218 Pick The Sticks (01背包变形)
    uestc oj 1217 The Battle of Chibi (dp + 离散化 + 树状数组)
  • 原文地址:https://www.cnblogs.com/jianhui-Ethan/p/4670399.html
Copyright © 2011-2022 走看看