Use cases for rvalue references

Rather than talk about principles in a long form, it is better to cite a few chestnuts first.

1. Function parameter passing
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);
}

As above, the lvalue of d passed by print is copied one less time than print1.
But in practical applications, we prefer the following way of writing, that is, using const references, the introduction is clear, and it also reduces unnecessary copies.

void print2(const DATA& d) {
    
     std::cout << d.value1 << ":" << d.value2 << std::endl; }
2. Move semantics

Move semantics is actually to implement the move constructor of the class .

#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;
}

The above code, if not optimized, will be copied twice. (The compiler turns on optimization by default)

[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!

Realize the move constructor by shallow copy

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

test

[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!

When the class contains both a copy constructor and a move constructor, if a temporary object is used to initialize the object of the current class, the compiler will call the move constructor first to complete this operation. Only when there is no suitable move constructor in the class, the compiler will go back and call the copy constructor.

reference

This is the movement semantics, it seems that there is not much, but it does greatly improve the performance of C++.

3. Perfect forwarding

Perfect forwarding may be used less in actual development.
The so-called perfect forwarding means that the function template can "perfectly" forward its own parameters to other internally called functions. The so-called perfect means not only can accurately forward the value of the parameter, but also ensure that the left and right value attributes of the forwarded parameter remain unchanged .

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

As above, if t is an lvalue in function1, then it is still an lvalue in function2; if t is an rvalue in function1, then it is still an rvalue in function2. This is perfect forwarding.
Obviously, the above template function does not achieve perfect forwarding. Because t in function2 must be an lvalue. In addition, if t is not a reference type, a copy will occur when function1 calls function2.
So how to achieve perfect forwarding? As follows, it's that simple, add &&

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;
}

Realize it, uh. . . It seems that the result is not quite right. Have to introduce a new function

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

Well, the output is right now.

If you haven't touched rvalue references before, then I think I can stop here for a while.
There must be many questions. What do std::move and forward do? Simply put, they can convert lvalues ​​into rvalues.

The above is just my simplest understanding of rvalue references. For more in-depth thinking, please refer to "Effective Modern C++"

Guess you like

Origin blog.csdn.net/niu91/article/details/109241744