Rvalue references (右值引用) and Move Semantics
Rvalue references are a new reference type introduced in C++0x that help solve the problem of unnecessary copying and enable perfect forwarding. When the right-hand side of an assignment is a rvalue, then the left-hand side object can steal resources from the right-hand side object rather than performing a separate allocation, thus enabling move semantics.
Lvalue: 既可以出现在 operator= 左侧,又可以出现在 operator= 右侧
Rvalue: 只能出现在 operator= 右侧
什么意思呢?
就是 Lvalue 既可以给其他变量赋值,又可以被常量或变量赋值。而 Rvalue 只能给其他变量赋值,自身不能被改变(赋值)。
对于整形来说:
int a = 9;
int b = 4;
a = b; //OK
b = a; //OK
a = a + b; //OK
a + b = 42; // [Error] lvalue required as left operand of assignment
对于 String 来说:
string s1("hello");
string s2("world");
s1 + s2 = s2; // 竟然通过了编译
cout << "s1: " << s1 << endl; //s1: hello
cout << "s2: " << s2 << endl; //s2: world
string() = "world"; // 竟然通过了编译。(temp obj 是 Rvalue 哦!)
move 操作为什么比 copy 操作更有效率呢?
首先为什么请证明 move 操作比 copy 更有效率,因此给出如下代码:
#include<iostream>
#include<ctime>
#include <cstdlib>
#include <vector>
#include <list>
#include <set>
#include <deque>
#include <string.h>
#include <string>
using namespace std;
class MyString {
public:
static size_t CC;
static size_t MC;
static size_t CA;
static size_t MA;
MyString() {
_pointer = nullptr;
_len = 0;
}
MyString(const char* p) {
_len = strlen(p);
initial(p);
}
// copy ctor
MyString(const MyString& str) {
_len = str.get_len();
initial(str.get_pointer());
++CC;
}
// copy assignment
MyString& operator=(const MyString& str) {
++CA;
if(*this != str){
if (_pointer != nullptr) delete _pointer;
_len = str.get_len();
initial(str.get_pointer());
}
return *this;
}
// move ctor
MyString(MyString&& str) noexcept {
_pointer = str.get_pointer();
_len = str.get_len();
str.set_len();
str.set_null();
++MC;
}
// move assignment
MyString& operator=(MyString&& str) noexcept {
++MA;
if(*this != str){
if (_pointer != nullptr) delete _pointer;
_pointer = str.get_pointer();
_len = str.get_len();
str.set_len();
str.set_null();
}
return *this;
}
~MyString() {
if (_pointer != nullptr) delete _pointer;
_pointer = nullptr;
}
void initial(const char* s) {
_pointer = new char[_len + 1];
memcpy(_pointer, s, _len);
_pointer[_len] = ' ';
}
char* get_pointer() const { return _pointer; }
size_t get_len() const { return _len; }
void set_null() { _pointer = nullptr; }
void set_len() { _len = 0; }
static void clear_count() {
CC = 0;
MC = 0;
CA = 0;
MA = 0;
}
bool operator==(const MyString& rhs) const {
return this->_pointer == rhs.get_pointer();
}
bool operator!=(const MyString& rhs) const {
return !(*this == rhs);
}
bool operator<(const MyString& rhs) const { //这里为什么参数加const,函数后面也加const?
return string(this->_pointer) < string(rhs.get_pointer()); //注意struct less中对 < 的重载就能得到答案了!
} //加了 const 的函数可以被非const 或 const 函数调用,但是非const函数只能被非const函数调用!所以这里函数后面不加const编译器会报错
private:
char* _pointer;
size_t _len;
};
size_t MyString::CC = 0;
size_t MyString::MC = 0;
size_t MyString::CA = 0;
size_t MyString::MA = 0;
template<typename Container, typename T>
void insert_elem(Container c, T val, size_t num) {
T::clear_count();
srand((int)time(0));
char buf[5];
clock_t start = clock();
for (size_t i = 0; i < num; i++) {
sprintf(buf, "%d", rand() % 10000);
auto end = c.end();
c.insert(end, T(buf));// 容器会调用 insert(_iterator, T&&) 版本,然后再调用 T 的 move ctor 版本来构造这个对象
}
clock_t finish = clock();
cout << "使用 move 版本来 insert 所用时间:" << finish - start << "ms" << endl << "Container's size: " << c.size() << endl;
printf("CC: %d MC: %d CA: %d MA: %d
", T::CC, T::MC, T::CA, T::MA);
Container c_t;
T::clear_count();
start = clock();
for (size_t i = 0; i < num; i++) {
sprintf(buf, "%d", rand() % 10000);
auto end = c_t.end();
T obj(buf);
c_t.insert(end, obj);// 容器会调用 insert(_iterator, T&) 版本,然后再调用 T 的 copy ctor 版本来构造这个对象
}
finish = clock();
cout << "使用 copy 版本来 insert 所用时间:" << finish - start << "ms" << endl;
printf("CC: %d MC: %d CA: %d MA: %d
", T::CC, T::MC, T::CA, T::MA);
start = clock();
Container c1(c); // copy ctor
finish = clock();
cout << "使用 copy ctor 所用时间:" << finish - start << "ms" << endl;
start = clock();
Container c2(move(c)); // move ctor
finish = clock();
cout << "使用 move ctor 所用时间:" << finish - start << "ms" << endl;
}
int main() {
size_t nums = 3000000;
vector<MyString> vi;
cout << "vector 开始进行测试:
";
insert_elem(vi, MyString(), nums);
cout << endl;
list<MyString> li;
cout << "list 开始进行测试:
";
insert_elem(li, MyString(), nums);
cout << endl;
deque<MyString> di;
cout << "deque 开始进行测试:
";
insert_elem(di, MyString(), nums);
cout << endl;
// set<MyString> si;
// cout << "set 开始进行测试:
";
// insert_elem(si, MyString(), nums);
return 0;
}
对于 set 容器,测试时出现问题(以我目前的能力还不知道哪里错了。。。。),之后找机会再看看。。。。
从结果可以看到的确 move 比 copy高效,在 move ctor 和 copy ctor 比较中 move 的优势更为明显。
其实 move 与 copy 最大的区别在于,move 是一种 steal 行为,它拷贝的是右值的地址,并把原来指向右值的那个隐形指针(我们看不到)置为 nullptr;而 copy 是纯粹的对内容的拷贝。或者在某种程度上来说 move 类似于浅拷贝,而 copy 类似于深拷贝,当然这么说会有点不恰当,但是比较容易理解。