美文网首页
C++ 右值引用 / 移动语义 / universal refe

C++ 右值引用 / 移动语义 / universal refe

作者: my_passion | 来源:发表于2020-05-05 16:46 被阅读0次

    1 右值引用

    A 
    GetA( ) { return A(); }
    
    GetA() return 右值 temp_obj 
    

    pass by value -> return 右值 temp_obj -> copy

    `第 1 / 2 行 code`
    

    A a = GetA(); / A&& a = GetA();

    右值 temp_obj 赋值 / 被绑定 -> copy / no copy

    右值引用 对象:右值 temp_obj lifetime 延续

    到 和 右值引用对象 a 一样长
    
    1.jpg 2.jpg

    2 move 语义

    `第 3 行 code`
    

    A(A&& rhs) : ptr(rhs.ptr) ) { rhs.ptr = nullptr; }

    1. 解决的问题

    class 含 ptr mem

    1) shallow copy ctor: only copy ptr

    =>
    

    同一 dynamic memory 被 delete > 1 次

    , 第 2次 delete 开始,
    

    指针悬挂

    2) deep copy: also copy memory

    =>
    

    不必要的 heap memory copy

    可能 1 份 heap memory 就能实现
    

    3) move

    solve 上面 2 个 Prob
    
    3.jpg 4.jpg

    2. 3 种 引用

    左 / 右 / 常量左 值引用: 只能 / 只能 / 可 绑定 左值 / 右值 / ( const / non-const ) 左 or 右值 obj, 用 T& / T&& / const T&

    绑定右值, 可减少一次 copy

    lambda expr 是 rvalue

    void AcceptVal(A a) { }
    void AcceptRef(const A& a) { }
    
    AcceptVal( GetA() ); // 应调 2 次 copy ctor
    AcceptRef( GetA() ); // 应调 1 次 copy ctor
    
    `实际上, 两者 1次 copy ctor 都没调`
     
    -> 
    

    compiler 返回值优化

    2) 不是所有的 return value 都能被 compiler 优化, 但可被 move 语义 优化

    3. 匹配顺序

    `右值 temp_obj copy / assignment / move 都有 时,` 
    
    `匹配顺序:`
    

    (1) 右值引用 > 常量左值引用

    `copy/move ctor/assignment + move 都有 时,`
    
    `匹配顺序:`
    

    (2) move 语义 > copy 语义

    `reason: move/copy 语义 的 function 
    只 / 可接受 右 / 左 or 右 值参数`
    
    // shallow / deep copy & move
    class A
    {
    private:
        int* ptr;  // ptr mem
    public:
        A() : ptr( new int(0) ) {}
    
        //(1) shallow copy ctor: only copy ptr
        A(const A& rhs) : ptr(rhs.ptr) { } 
    
        //(2) deep copy ctor   : also copy memory 
        A(const A& rhs) : ptr(new int(*rhs.ptr) ) { }  
    
        //(3) move: copy ptr + src ptr set NULL
        A(A&& rhs) : ptr(rhs.ptr) ) { rhs.ptr = nullptr; } 
    
        ~A(){ delete ptr; }
    };
    
    A 
    GetA(){ return A(); }
    
    int main()
    {
        A a = GetA();
    }
    
    // string 的 move 语义
    class MyString
    {
    private:
       char* pc;
    public:
        // copy assignment
        MyString& 
        operator=(const MyString& rhs)
        {
            if (this == &rhs) 
                return *this;
    
            delete[] pc;
            
            pc = new char[ strlen(rhs.pc)  +  1];
            strcpy(pc, rhs.pc);
            
            return *this;
        }
    
        // move assignment
        MyString& 
        operator=(MyString&& rhs) noexcept
        {
            if (this == &rhs) 
              return *this;
    
            delete[] pc;
            
            pc     = rhs.pc;    
            rhs.pc = nullptr; 
            
            return *this;
        }
        
        ~MyString() { delete[] pc; }
    };
    

    3 universal ( 通用 / undefined ) references => 左 or 右 值引用

    (1) T&& 与 template 结合, 且 自动类型推导 ( func template ) 时, T&& 类型不确定 需要推导 => T&& 才是 universal references

    (2) universal references: 传递 左/右 值, 就是 左/右值引用

    完美转发 正利用该特性

    template<typename T>
    void f( T&& para){}
    
    //(1) => T = int => + && -> int&&
    f(10);  // arg 是右值 => T推导成 int => para 是 int&&, 是 右值引用
    
    int x = 10; 
    //(2) => T = int& => + && -> int&
    f(x);   //arg 是左值 => T 推导成 int& => para 是 int&&& 折叠成 int&, 是 左值引用
    
    //1
    template<typename T>
    void f( T&& para); // 调 f 时, T 需推导 => para: universal references 
    
    //2  
    template<typename T>
    class A{
      A(A&& a); //(1)A type 确定 => para ( a ): 右值引用
    };
    
    //3 vector<T> 的 type 必须确定 => T type 必须确定 
    // => para 无需推导 => para: 右值引用
    template<typename T>
    void f(std::vector<T>&& para); 
    

    4 完美转发

    `第 4 行 代码`
    
    // 完美转发 std::forward
    template <typename T>
    void f(T&& val)   // val : universal references 
    {
        foo( std::forward<T>(val) );
    }
    

    完美转发 ( perfect forward ): func template 的 para 作 arg 传给 另一 forward func 时, 左/右值特性 不变

    函数调用 之 形实结合: 按 arg value 的 左右值属性 ( lvalue / rvalue ) 匹配 相应 para type

    背景
    

    (1) 无 universal reference 时 => value 经 2 次 转发 => rvalue 变 lvalue

    void f2(int&& para2) {}
    void f2(const int& para2) {} //para2:const lvalue ref -> arg = lvalue / rvalue
    
    // arg (val): lvalue -> 应调 f1(int& para1) => T = int -> + & = int&
    //      -> para1 作 arg: lvalue -> 调 f2(int& para2)
    template <typename T>
    void f1(T& para1)  { f2(para1); }
    
    
    // arg: (0) rvalue -> 应调 f1(const int& para1) => T = int -> + & = const int&
    //      -> para1 作 arg: lvalue -> 调 f2(const int& para2)
    template <typename T>
    void f1(const T& para1) { f2(para1); }
    
    int val = 0;
    f1(val); // (1)
    
    f1(0); // (2)
    

    (2) 仅 universal references => 2 次转发 后, maybe rvalue 变 lvalue

    void f2(int&& para2) {}
    void f2(int& para2) {]
    
    template <typename T>
    void f1(T&& para1) { f2(para1); }
    
    // arg: lvalue -> 应调 f1(int& para1) => T = int&
    //  -> para1 作 arg: lvalue -> 调 f2(int&)
    f1(val);
    
    // arg: rvalue -> 应调 f1(int&& para1) => T = int
    //  -> para1 作 arg: lvalue -> 调 f2(int&)
    f1(0); 
    

    (3) universal references + 完美转发

    void f2(int&& para2) { }
    void f2(int&  para2) { }
    
    // arg (val) lvalue -> 应调 f1(int& para1) => T = int& -> + && = int&
    //      -> ...para1 作 arg: value = lvalue -> 调 f2(int& para2)
    
    // arg: (val) rvalue -> 应调 f1(int&& para1) => T = int -> + && = int&&
    //      -> ...para1 作 arg: value = rvalue -> 调 f2(int&& para2)
    template <typename T>
    void f1(T&& para1)
    {
        f2( std::forward<T>(para1) ); // 按参数 val 的 value 实际类型 转发
    }
    
    int val = 0;
    f1(val); 
    f1(0);
    

    5 std::move() C++11

    `1. 背景`
    

    对象 含 move 语义 的 func

    std::string s1 = "hello";
    std::string s2;
    

    (1) 右值 obj 作 arg: 隐含调 move=

    // ctor + move=: string& operator=(srting&&);
    s2 = std::string("world"); 
    

    (2) 左值 obj 直接作 arg: 匹配调 copy=

    s2 = s1; // copy= : string& operator = (const string& );
    

    (3) 想 左值 obj 作 arg : 匹配调 move=

    solu: 
    

    std::move wrap 左值 obj 为 右值引用 -> 作 arg: 以 匹配调 move=

    // string& operator=(const string&&);
    s2 = std::move(s1); 
    
    =>
    

    解决的问题:

    wrap 左值 obj 为 右值引用 -> 作 arg: 以 匹配调用 obj_class 的 move 语义 func, 以 move 左值 obj 的 internal handle / heap memory / dynamic array

    因为 左值 obj 直接 作 arg, 匹配调用的是 copy 语义 func

    `2. 机制`
    

    将 arg ( 左值/右值 obj ) 强转为 右值引用

    3. 具有 move 语义条件

    对象 含 handle / heap memory / dynamic array + move 语义 的 func

    否则, std::move() 是 `copy 语义`
    
    =>
    

    移动语义 的 地方 应该总是用 std::move 将 obj ( 可能是 temp_object ) 转换为 右值引用 -> 没 move 函数 时, std::move 默认 copy 操作

    void 
    pop(T& value)
    {
        // ..
        value = std::move( in_stk.top() ); // copy / move 语义
        in_stk.pop(); // destory T 型 data_item 本身
    }
    

    top: pass by reference
    pop: destory

    相关文章

      网友评论

          本文标题:C++ 右值引用 / 移动语义 / universal refe

          本文链接:https://www.haomeiwen.com/subject/zhwmghtx.html