Rvalue references

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 ctorcopy ctor 比较中 move 的优势更为明显。

其实 move 与 copy 最大的区别在于,move 是一种 steal 行为,它拷贝的是右值的地址,并把原来指向右值的那个隐形指针(我们看不到)置为 nullptr;而 copy 是纯粹的对内容的拷贝。或者在某种程度上来说 move 类似于浅拷贝,而 copy 类似于深拷贝,当然这么说会有点不恰当,但是比较容易理解。

原文地址:https://www.cnblogs.com/Codroc/p/13998432.html