zoukankan      html  css  js  c++  java
  • 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 类似于深拷贝,当然这么说会有点不恰当,但是比较容易理解。

  • 相关阅读:
    vue Ant Design 树形控件拖动限制
    defineProperty介绍及使用
    webpack 配置入门
    vscode 插件
    解决输入框自动填充账号密码的问题
    css 动画
    vue按钮权限控制
    git操作
    TCP和UDP的区别
    通信协议 HTTP TCP UDP
  • 原文地址:https://www.cnblogs.com/Codroc/p/13998432.html
Copyright © 2011-2022 走看看