C++ 右值引用 / 移动语义 / universal reference / 完美转发: 4行代码的故事

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
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
void f( T&& para); // 调 f 时, T 需推导 => para: universal references 

//2  
template
class A{
  A(A&& a); //(1)A type 确定 => para ( a ): 右值引用
};

//3 vector 的 type 必须确定 => T type 必须确定 
// => para 无需推导 => para: 右值引用
template
void f(std::vector&& para); 

4 完美转发

`第 4 行 代码`
// 完美转发 std::forward
template 
void f(T&& val)   // val : universal references 
{
    foo( std::forward(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 
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 
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 
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 
void f1(T&& para1)
{
    f2( std::forward(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 reference / 完美转发: 4行代码的故事)