右值引用的使用场景

与其长篇大论的讲原理,不如先举几个栗子。

1. 函数传参
struct DATA {
    
    
    string value1;
    string value2;
    DATA(string v1, string v2) : value1(v1), value2(v2) {
    
    
        std::cout << "DATA" << std::endl;
    }
    DATA(const DATA& c) {
    
     std::cout << "copy DATA" << std::endl; }
    ~DATA() {
    
     std::cout << "~DATA" << std::endl; }
};

void print(DATA&& d) {
    
     std::cout << d.value1 << ":" << d.value2 << std::endl; }
void print1(DATA d) {
    
     std::cout << d.value1 << ":" << d.value2 << std::endl; }


void test1() {
    
    
    DATA d("5", "function");
    print(std::move(d));
    print1(d);
}

如上,print传入d的左值比print1少一次拷贝。
但在实际应用中,我们更喜欢下面的写法,即使用const引用,简介明了,同样也减少了不必要的拷贝。

void print2(const DATA& d) {
    
     std::cout << d.value1 << ":" << d.value2 << std::endl; }
2. 移动语义

移动语义,其实就是实现类的移动构造函数

#include <iostream>
using namespace std;

class demo {
    
    
   public:
    demo() : num(new int(0)) {
    
     cout << "construct!" << endl; }
    //拷贝构造函数
    demo(const demo &d) : num(new int(*d.num)) {
    
    
        cout << "copy construct!" << endl;
    }
    ~demo() {
    
     cout << "class destruct!" << endl; }

   private:
    int *num;
};

demo get_demo() {
    
     return demo(); }

int main() {
    
    
    demo a = get_demo();
    return 0;
}

上面的代码,如果不经过优化,会进行两次copy。(编译器默认开启优化)

[root@localhost test-codes]# g++ -std=c++11  -fno-elide-constructors 02con.cpp -o 02con
[root@localhost test-codes]# ./02con 
construct!
copy construct!
class destruct!
copy construct!
class destruct!
class destruct!
[root@localhost test-codes]# g++ -std=c++11  02con.cpp -o 02con
[root@localhost test-codes]# ./02con 
construct!
class destruct!

通过浅拷贝方式实现移动构造函数

//移动构造函数
    demo(demo &&d):num(d.num){
    
    
        d.num = NULL;
        cout<<"move construct!"<<endl;
    }

测试

[root@localhost test-codes]# g++ -std=c++11  -fno-elide-constructors 02con.cpp -o 02con
[root@localhost test-codes]# ./02con 
construct!
move construct!
class destruct!
move construct!
class destruct!
class destruct!

当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。

参考

这就是移动语义,似乎看起来没多少东西,但确实大大提高了C++的性能。

3. 完美转发

完美转发,在实际开发当中,可能用的比较少。
所谓完美转发就是指函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变

template<typename T>
void function1(T t) {
    
    
    function2(t);
}

如上,t在function1中是左值,那么在function2中还是左值;t在function1中是右值,那么在function2中还是右值。这就是完美转发。
很明显上面这个模板函数,并没有实现完美转发。因为function2中的t肯定是左值。另外如果t不是引用类型,function1调用function2时还会发生一次拷贝。
那么怎么实现完美转发呢?如下,就是这么简单,加&&

void function2(int &t) {
    
     cout << "左值" << endl; }
void function2(const int &t) {
    
     cout << "右值" << endl; }

template <typename T>
void function1(T &&t) {
    
    
    function2(t);
}

int main() {
    
    
    function1(100);  
    int num = 333;
    function1(std::move(num));
    function1(num);
    return 0;
}

实现一下,额。。。好像结果不太对。还得引入一个新的函数

template <typename T>
void function1(T &&t) {
    
    
    function2(forward<T>(t));
}
[root@localhost test-codes]# ./02con 
右值
右值
左值

好了这下输出对了。

如果之前没有接触过右值引用,那么到这里我想可以先停一停。
一定会有很多疑问,std::move和forward是做什么的?简单说他们可以把左值转换成右值。

上述只是我对右值引用最简单的理解,其他更深入的思考可以参考《Effective Modern C++》

猜你喜欢

转载自blog.csdn.net/niu91/article/details/109241744