[心得] c++ 11 的 move semantic

楼主: descent (“雄辩是银,沉默是金”)   2016-06-05 23:44:54
个人学习速度慢, 2016 了才学到 2011 的东西。
精华区有个很仔细的版本, 我这篇不那么长。
之前稍微接触过 c++ 11 move semantic, 那时候似懂非懂不是很清楚这是干嘛的, 我一开
始还把 move semantic 和 return value optimization 搞混了, return value
optimization 并不需要 move semantic 才能做到。
再看过 rust 的 move semantic 之后, 才知道这东西的应用, rust 逼开发人员要搞懂这
个, 不是坏事, 但我喜欢 c++, 让我们从 c++ 来理解这个概念。
为了支援 move semantic, c++ 11 引入了 rvalue reference,
写成 &&, ex: int &&ri, ri 就是一个 rvalue reference type。
所以 ctor 又多了一个 rvalue 的版本 (ref L28); 当然 operator=
也是, 创造一个 class 要愈写愈多程式码。
rust 的这个概念是为了内存安全性, c++ 则是为了性能。简单来说就是希望一个
object 不需要作 copy 的动作。什么叫作“不需要作 copy 的动作”?
你可能需要自己写一个类似 std::string (这是一个练习写 class 的好方法) 的 class
才能理解 move semantic, 我刚好有一个现成的, 拿来理解这样的行为正好, 顺便支援
move semantic。DS::string 就是我的模仿品, 里头有个 char *str_, 复制一个
DS::string 需要连 str_ 一起复制, 若是用 move 则是把原本的 s1::str_ 转到
s2::str_, 那你一定会问:“转过去了, s1::str_ 剩下什么? 什么又是《转过去》?”答
案是你给他什么就是什么了。一般会给他 nullptr, 免得解构函式发动时出问题, 因为解
构函式会执行 delete [] str_, 我是给 0。这就是为什么 move 后原来的 s1::str_ 不
能用, 不过 c++ compiler 可不像 rust 会帮你挡下来, 我在这个范例上还是照用, 结果
就是预期的 null pointer。
复制为什么比较慢, 看以下的程式码行为应该就可以理解了:
复制: strcpy(s2::str_, s1:str_)
move : s2::str_ = s1:str_
比较奇怪的是 L118 若改成 void f3(DS::string &&s) 反而不会发动 move ctor, 这里
我不是很理解。
ANS
这个在我反组译之后有了新的理解, 类似传 reference (pointer), 根本不会发动 (也用
不著) 任何 ctor。
L141 std::move 就是来把这个 object 变成 rvalue, 用 f3((DS::string &&)(s1)); 也
可以, 这样才能发动 move ctor, L28 那个有两个 && 就是 move ctor; 否则只会发动
copy ctor, 这就没有节省执行时间了。
mystring.cpp
1 #include "mystring.h"
2 #include "myiostream.h"
3 #include "mem.h"
4
5 #ifdef TEST
6 #include <cstdio>
7 using namespace std;
8 #else
9 // #define std DS
10 #endif
11
12 DS::string::string():len_(0), str_(0)
13 {
14 #ifdef TEST
15 std::printf("1 ctor\n");
16 #endif
17 }
18
19 DS::string::string(const char *str)
20 {
21 generate_string(str, s_strlen(str));
22
23 #ifdef TEST
24 std::printf("const char *str ctor\n");
25 #endif
26 }
27
28 DS::string::string(string &&s)
29 {
30 str_ = s.str_;
31 len_ = s.len_;
32 s.str_ = 0;
33 s.len_ = 0;
34 #ifdef TEST
35 std::printf("move ctor\n");
36 #endif
37 }
38
39 DS::string::string(const string &s)
40 {
41 generate_string(s.c_str(), s.length());
42
43 #ifdef TEST
44 std::printf("copy ctor\n");
45 #endif
46 }
47
48 DS::string::~string()
49 {
50 #ifdef TEST
51 std::printf("11 dtor:%s\n", str_);
52 #endif
53 delete [] str_;
54 //cout << "string ~ctor" << endl;
55 }
56
113
114 #ifdef TEST
115 #include <stdio.h>
116 #include <utility>
117
118 void f3(DS::string s)
119 {
120 printf("f3 s: %s\n", s.c_str());
121 }
122
123 DS::string f2()
124 {
125 DS::string s1{"f2"};
126 return s1;
127 }
128
129 DS::string f1()
130 {
131 std::printf("bb\n");
132 DS::string s1{"return str"};
133 std::printf("ee\n");
134 return s1;
135 }
136
137 int main(int argc, char *argv[])
138 {
139 DS::string s1=f2();
140 printf("s1: %s\n", s1.c_str());
141 f3(std::move(s1));
142 printf("s1: %s\n", s1.c_str());
143 printf("s1.length(): %d\n", s1.length());
144
183 return 0;
184 }
185 #endif
list 1. 执行结果
1 const char *str ctor
2 s1: f2
3 move ctor
4 f3 s: f2
5 11 dtor:f2
6 s1: (null)
7 s1.length(): 0
8 11 dtor:(null)
rust 默认行为是 move semantic, 我不知道这是不是好事情, 不过和 c++ move
semantic 一样, 都需要一点点的专业知识才能理解, 这是他们的高门槛。
最后想问, 大费周张搞了个 move semantic 可以用在哪些地方? 毕竟宣告了一个
object, 几乎都会在继续使用, 如果传给一个 function 后就不能用了, 那写起来会很不
习惯, rust 就是这样, 容器是一个很好的应用, 把 object 放进容器之后, 就可以用容
器的 object, 并不在需要用这个 object 来操作, 所以放进容器的 object 就很适合
move semantic。另外有个网友补充了两点, 感谢。
下面的参考连结写的不错, 不过我有自信你能看懂这篇的话, 应该不需要看以下两个连结
, 但是其提供的《c++ copy and swap idiom 用法 ( https://goo.gl/NU7HmE )》让我非
常受用。
ref:
翻:怎理解 C++ 11中的move(基)
作者: chiwa (我是青蛙,不是王子^_^)   2016-06-06 20:10:00
rvalue 不等于 rvalue reference
作者: Dannvix (Dan)   2016-06-06 20:17:00
延伸阅读Scott Meyers on Uni. Ref. http://j.mp/1su76iV

Links booklink

Contact Us: admin [ a t ] ucptt.com