参考文章https://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html
问题引出
一个例子
problem.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <iostream> #include <string.h> class String { public: String(const char *s) { std::cout << "initlize\n"; size_ = strlen(s) + 1; data_ = new char[size_]; strcpy(data_, s); }
~String() { std::cout << "call deconstructor\n"; delete []data_; }
String(const String &s) { std::cout << "call copy constructor\n"; size_ = s.size_; data_ = new char[size_]; memcpy(data_, s.data_, size_); }
friend std::ostream& operator<<(std::ostream& out, const String &s) { if (s.size_ == 0 || s.data_ == NULL) { return out; } out << s.data_; return out; } public: int size_; char *data_; };
String test() { String str("hello"); return str; } int main(void) { String s = test(); std::cout << "in main function\n"; return 0; }
|
让我们编译运行一下:
需要注意使用编译选项 -fno-elide-constructors关闭RVO(Return Value
Optimization),否则现在的编译器会给你自动优化
1
| g++ problem.cc -o a.out -fno-elide-constructors
|
运行的结果如下:
1 2 3 4 5 6 7
| 第一行 initlize 第二行 call copy constructor 第三行 call deconstructor 第四行 call copy constructor 第五行 call deconstructor 第六行 in main function 第七行 call deconstructo
|
我们可以看到该程序调用了两次拷贝构造函数和三次析构函数。那么这个程序的流程是怎么样的呢?下面作者将对每一行输出做出解释。
输出解释
第一行
在调用test函数之后,String str("hello")会调用构造函数构造对象str
,对应输出的结果initlize。
第二行
当test函数返回str的时候,它返回的是一个临时对象,这个临时对象是通过对str拷贝而构造的,而不是直接返回的str本身。因为str只是test函数中的局部变量,它的作用域只在test函数间,不可能跳到函数外再传给主函数中的s,因此test函数返回的只是一个临时变量。
第三行
上面已经阐明,test函数返回的只是一个临时变量。所以当test函数退出的时候,局部变量str将会被析构。对应第三行的输出。
第四行
在main函数中,test返回的临时变量会通过拷贝构造函数赋值给s,因此对应第四行中的拷贝构造函数。
第五行
当临时变量复制给s之后,这个临时变量也就被析构了。因此对第五行的输出。
第六行
本行的输出在临时变量析构之后发生,在s析构之前输出。
第七行
main函数中的局部变量s析构。
问题说明
我们可以看到,test函数本来应该返回的str,但其实返回的是一个临时变量,这就导致了不必要的拷贝构造。
那么我们有什么办法来解决这个问题呢?将test函数的返回值变成引用类型String &行不行呢?
这显然是不行的,因为str只是一个临时变量,当test函数结束的时候就会被释放,如果引用一个将被释放的值可能会出错。
因此对于临时变量这一问题,C++引入了右值(rvalue)和std::move这两个概念。
右值(rvalue)和std::move
什么是右值呢,它与左值相对于。左值也就是我们的变量之类的,它可以放在等号的左边。而右值可以理解为临时变量,C++使用&&作为右值引用的符号。而std::move可以将左值变为右值。
算了,不想写了,就这样吧。把代码贴上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include <iostream> #include <string.h> #include <string> class String { public: String(const char *s) { std::cout << "initlize\n"; size_ = strlen(s) + 1; data_ = new char[size_]; strcpy(data_, s); }
~String() { std::cout << "deallocate string:" << "\n"; delete []data_; }
String(const String &s) { size_ = s.size_; data_ = new char[size_]; memcpy(data_, s.data_, size_); std::cout << "allocate and copy string\n"; }
String(String &&s) { size_ = s.size_; data_ = s.data_; s.size_ = 0; s.data_ = nullptr; }
friend std::ostream& operator<<(std::ostream& out, const String &s) { if (s.size_ == 0 || s.data_ == NULL) { return out; } out << s.data_; return out; } public: int size_; char *data_; };
String test() { String str("test"); return str; }
int main(void) { String s("hello"); String b = std::move(s); std::cout << b << ", " << s << "\n";
std::string ss = "hle"; std::string bb(std::move(ss)); std::cout << bb << std::endl; String a("hello"); return 0; }
|