zoukankan      html  css  js  c++  java
  • c++的左值(lvalue),右值(rvalue),移动语义(move),完美转发(forward)

    c++的左值(lvalue),右值(rvalue),移动语义(move),完美转发(forward)

    c++的左值,右值 精辟总结

    当一个对象被用作右值的时候,使用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)左值右值,完美转发参考文档

    左值持久,右值短暂;move:显示地将一个左值转换为对应右值的引用类型,还可以获取绑定到左值上的右值引用,int&& rr3 = std::move(rrl); 使用move就意味着除了对rrl赋值或销毁它外,我们不再使用它。

    std::forward()与std::move()相区别的是,move()会无条件的将一个参数转换成右值,而forward()则会保留参数的左右值类型,可以使用std::forward实现完美转发

    移动语义解决了无用拷贝的问题:移动构造函数

    右值引用:函数的返回值

    int& 左值引用

    int&& 右值引用

    c++中无用拷贝的情况

    /*类里面 没有移动构造函数
    这样就会使用 copy construct function,会导致大量无用的 memory copy。
    */
    class Test {  
    public:
        string desc; 
        int * arr{nullptr};
        Test():arr(new int[5000]{1,2,3,4}) { 
            cout << "default constructor" << endl;
        }
        Test(const Test & t) {
            cout << "copy constructor" << endl;
            if (arr == nullptr) arr = new int[5000];
            copy(t.arr,t.arr+5000, arr);
        }
        ~Test(){
            cout << "destructor " << desc << endl;
            delete [] arr;
        }
    };
    
    Test createTest() {
        return Test();
    }
    
    int main(){
    
        Test reusable;
        reusable.desc = "reusable";
        Test duplicated(reusable);
        duplicated.desc = "duplicated";
    
        Test t(createTest());
        t.desc = "t";
    
        cout<<"end"<<endl;
    }
    
    

    运行结果

    default constructor
    copy constructor
    default constructor
    end
    destructor t
    destructor duplicated
    destructor reusable
    

    使用移动语义避免无用的拷贝

    /*使用移动 construct function,避免无用的memory copy。
    */
    
    class Test {   
        public:
        string desc;
        int * arr{nullptr};
        Test():arr(new int[5000]{1,2,3,4}) { 
            cout << "__default constructor" << endl;
        }
        Test(const Test & t) {
            cout << "__copy constructor" << endl;
            if (arr == nullptr) arr = new int[5000]; //在这里要将 t.arr 置为空,因为经过move之后,我们认为不在使用这个值了,避免在新的对象中把指针释放后,原来的对象中存在野指针的现象
            copy(t.arr,t.arr+5000, arr);
        }
        Test(Test && t): arr(t.arr) {
            cout << "__move constructor" << endl;
            t.arr = nullptr;
        }
        ~Test(){
            cout << "..destructor " << desc << endl;
            delete [] arr;
        }
    };
    
    Test createTest(string str) {
        Test rt;
        rt.desc = str;
        cout<<"createTest:"<<&rt<<endl;
        return rt;
    }
    
    void main(){
        Test reusable;
        reusable.desc = "reusable";
        cout<<"reusable.arr "<<reusable.arr<<endl;
        
        Test duplicated(std::move(reusable));
        duplicated.desc = "duplicated";
        cout<<"reusable.arr "<<reusable.arr<<endl;
        cout<<"duplicated.arr "<<duplicated.arr<<endl;
    
        cout<<"rvalue--"<<endl;
        Test&& rt1 = createTest("rval");      //使用右值引用接收
        cout<<"rt1.arr "<<rt1.arr<<endl;
        
        cout<<"no rvalue--"<<endl;
        Test rt2 = createTest("normalVal");      //不使用右值引用接收,可以看到这里比使用右值引用接收 多了一次构造和析构(createTest中的临时对象)
        cout<<"createTest:"<<&rt2<<endl;        //尴尬,其实这里编译器已经做了优化了,可以看到第地址一样
        cout<<"rt2.arr "<<rt2.arr<<endl;
    
        cout<<"end"<<endl;
    }
    

    输出结果

    __default constructor
    reusable.arr 0x56521b946e70
    __move constructor
    reusable.arr 0
    duplicated.arr 0x56521b946e70
    rvalue--
    __default constructor
    createTest:0x7ffd092ea390
    rt1.arr 0x56521b94c0b0
    no rvalue--
    __default constructor
    createTest:0x7ffd092ea3c0
    createTest:0x7ffd092ea3c0
    rt2.arr 0x56521b950ee0
    end
    ..destructor normalVal
    ..destructor rval
    ..destructor duplicated
    ..destructor reusable
    

    左值引用右值引用

    //左值引用和右值引用
    void foo(const int & i) { cout << "const int & " << i << endl; }
    void foo(int & i) {  cout << "int & " << i << endl; }
    void foo(int && i) { cout << "int && " << i << endl; }
    void foo(const int && i) { cout << "const int && " << i << endl; }
    void main(){
        int i = 2;
        foo(i);
        foo(2);
        foo([]()->const int && {return 2;}());
    }
    

    完美转发

    /*在main当中调用relay,Test的临时对象作为一个右值传入relay,在relay当中又被转发给了func,那这时候转发
    给func的参数t也应当是一个右值。也就是说,我们希望:当relay的参数是右值的时候,func的参数也是右值;当
    relay的参数是左值的时候,func的参数也是左值。
    */
    class Test {   
        public:
        int * arr{nullptr};
        Test():arr(new int[5000]{1,2,3,4}) { 
            cout << "default constructor" << endl;
        }
        Test(const Test & t) {
            cout << "copy constructor" << endl;
            if (arr == nullptr) arr = new int[5000];
            copy(t.arr,t.arr+5000, arr);
        }
        Test(Test && t): arr(t.arr) {
            cout << "move constructor" << endl;
            t.arr = nullptr;
        }
        ~Test(){
            cout << "destructor" << endl;
            delete [] arr;
        }
    };
    
    template <typename T>
    void func(T t) {
        cout << "in func" << endl;
    }
    
    template <typename T>
    void relay(T&& t) {
        cout << "in relay" << endl;
        func(t);
    }
    //完美转发
    template <typename T>
    void relay1(T&& t) {
        cout << "in relay " << endl;
        func(std::forward<T>(t));
    }
    
    void main() {
        // relay(Test());
        // cout<<"end"<<endl;
    
        relay1(Test());
        cout<<"end"<<endl;
    
    }
    

    更多编程资料见公众号 xutopia77

  • 相关阅读:
    函数与导数部分的题型梳理
    构造函数习题1
    破解构造函数问题
    函数的值域
    函数的定义域
    高三数学微课堂
    Redux Todos Example
    Linux下查看Nginx安装目录、版本号信息及当前运行的配置文件
    antd的Tree控件实现点击展开功能
    Redux Counter example
  • 原文地址:https://www.cnblogs.com/xutopia/p/15782047.html
Copyright © 2011-2022 走看看