• 现代C++(Modern C++)基本用法实践:三、移动语义


    概述

    移动

    移动(move)语义C++引入了一种新的内存优化,以避免不必要的拷贝。在构造或者赋值的时候,如果实参是右值(或者左值由std::move转换成右值),便会匹配移动语义的函数调用如下述举例的Str(Str&& obj)

    移动语义的本质是将资源(内存/句柄)转移给另一个对象,被转移资源的对象不应再被使用。(这个概念有点像仙侠小说中的夺舍,夺舍成功的人获取被夺舍的人的身体(资源)),如下面伪代码:

    class Obj
    {
    	data
    	Obj(){
    		data = malloc(100)
    	}
    	// 移动 (夺舍)
    	Obj(Obj&& other){
    		data = other.data
    		other.data = nullptr
    	}
    }
    

    右值

    右值直观理解是等号右边的值(大概如此,并不准确),右值的概念指代的东西比较多,大概是指不可寻址的值(也有例外)。我觉得这个不必太过纠结,记住几个常见的即可:

    • 临时对象:如函数返回的临时对象(下面有举例)
    • 字面量
    • 显式std::move()转换的值
    • 没有捕获参数的lambda

    C++ 值类别表

    在 C++11之后,C++根据

    • 被标识:可通过不同标识符指代同一实体。(对象/内存)
    • 可移动:可作为移动语义函数的参数,例如移动构造,移动赋值。

    将值分为以下类别:

    • 泛左值:被标识
      • 左值:被标识且不可移动
      • 将亡值:被标识可移动
    • 右值:可移动
      • 将亡值:被标识可移动
      • 纯右值:不被标识且可移动

    用法举例

    参考测试项目代码ModernCppTest/modrenc_rvalueref_stdmove.cpp
    主要内容:

    • 移动语义下的构造和赋值
    • 移动还是拷贝的重载匹配
    • C++ 优化临时对象(连加产生的中间临时对象)尝试调用移动语义
    #include "ModernCppTestHeader.h"
    #include 
    using std::string;
    
    namespace n_rvalueref {
    	class Str {
    	public:
    		Str() {
    			LOG("无参构造");
    			this->str = new string();
    		}
    
    		Str(const string& str) {
    			LOG("有参构造 str = " << str);
    			this->str = new string(str);
    		}
    
    		Str(const Str& obj) {
    			LOG("拷贝构造 obj.str = " << *obj.str);
    			this->str = new string(*obj.str);
    		}
    
    		Str(Str&& obj) noexcept {
    			LOG("移动构造 obj.str = " << *obj.str);
    			this->str = std::move(obj.str);
    			// 被移动的对象不应该再被使用了
    			obj.str = nullptr;
    		}
    
    		Str& operator=(Str&& v) noexcept {
    			LOG("移动语义 operator = ");
    
    			if (this != &v) {
    				this->str = std::move(v.str);
    			}
    
    			return *this;
    		}
    
    		Str operator+(const Str& v)
    		{
    			string s = *this->str + *v.str;
    			return Str(s);
    		}
    
    		void Log()
    		{
    			LOG(str);
    		}
    
    		string* str;
    	};
    }
    
    using n_rvalueref::Str;
    
    // 右值引用&移动语义
    void rvalueref_stdmove_test()
    {
    	LOG_FUNC();
    
    	LOG_TAG("拷贝构造");
    	{
    		Str t1("A");
    		Str t2 = t1;
    		LOG_VAR(*t2.str);
    	}
    
    
    	LOG_TAG("移动构造, 注意被移动的对象t1不应再被使用");
    	{
    		// t1是左值,使用std::move强制转换成右值
    		Str t1("A");
    		Str t2 = std::move(t1);
    		LOG_VAR(*t2.str);
    	}
    
    
    	LOG_TAG("移动语义的运算符重载,注意运算符重载发生赋值运算(这个例子),而不是构造运算(上个例子)");
    	{
    		Str t1("A");
    		Str t2;
    		t2 = std::move(t1);
    	}
    
    
    	LOG_TAG("除了上述显示使用std::move转换,常见的容易忽视的发生移动构造场合列举");
    	{
    		LOG("---1 连续加法产生的临时对象,c++会尝试使用移动语义进行优化");
    		Str t1("A");
    		Str t2("B");
    		Str t3("C");
    		Str t4;
    		t4 = t1 + t2 + t3;
    
    		LOG("---2 函数返回的临时对象,c++会尝试使用移动语义进行优化");
    		auto f = []() {
    			auto s = Str("Hi"); 
    			return s; 
    		};
    		Str t5 = f();
    
    		/*
    		- 在容器中插入或删除元素:比如 std::vector::push_back,如果传递给它的是右值,它就会使用移动语义。
    		- 在标准库算法中:许多标准库算法,比如 std::sort,std::partition 等,在进行元素交换时会使用移动语义。
    		- 在 std::swap 中:std::swap 会使用移动语义来交换两个对象。
    		*/
    	}
    }
    
  • 相关阅读:
    基于Echarts的22个可视化大盘静态前台页面
    【HarmonyOS】【DevEco ohpm ERROR: NOTFOUND package “@ohos/hypium“如何解决
    iNFTnews | 元宇宙的欢乐世界:别开生面的游戏、音乐会、主题公园和电影
    吐槽 B 站收费,是怪它没钱么?
    【GIT版本控制】--什么是版本控制
    [RoarCTF 2019]Online Proxy
    Immutable是什么?
    常用的设计模式
    使用docker搭建DVWA
    K8S常用kubectl命令汇总(持续更新中)
  • 原文地址:https://www.cnblogs.com/hggzhang/p/17523881.html