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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342