
文章目录一、表达式值类别Value CategoriesC11起1.1 分类体系1.2 亡值(xvalue)四类标准产生场景1.3 引用绑定规则1.4 浅拷贝、深拷贝值类别与拷贝行为基础1.5 值类别控制移动构造触发二、移动语义、拷贝消除 三/五/零法则2.1 移动语义2.2 拷贝消除 Copy Elision2.3 三五零法则三、RAII 与资源所有权模型3.1 RAII 资源获取即初始化3.2 三种所有权模型3.3 weak_ptr配合shared_ptr3.4 接口设计通过类型显式表达所有权四、内存编码工程规范知识点链路表达式值类别 → 引用绑定/拷贝移动 → 拷贝消除 → 三五零资源管理准则 → RAII 所有权模型 → 工程内存编码规范✅参考学习网址https://cppreference.cn/w/一、表达式值类别Value CategoriesC11起1.1 分类体系三大基础类别左值lvalue、纯右值prvalue、亡值xvalue复合分类泛左值 g l v a l u e l v a l u e x v a l u e \mathbf{泛左值glvalue lvalue xvalue}泛左值glvaluelvaluexvalue右值 r v a l u e p r v a l u e x v a l u e \mathbf{右值rvalue prvalue xvalue}右值rvalueprvaluexvalue说明标准无“纯左值”术语工程口语指代不含亡值的普通左值正式文档禁用。类别核心特征代码示例左值 lvalue拥有持久内存地址对象生命周期较长int a 10; a数组名返回T函数调用亡值 xvalue泛左值、属于右值资源允许被转移对象即将失效std::move(a)调用返回T的函数纯右值 prvalue临时对象无持久存储没有可供转移的长期资源12std::string(hello)字面临时对象1.2 亡值(xvalue)四类标准产生场景std::move(obj)将左值强制转为亡值底层等价static_castT(obj)std::forwardT(obj)完美转发产生右值引用表达式亡值调用返回类型为右值引用 T的函数对亡值进行成员访问std::move(s).size()1.3 引用绑定规则std::string str{test};std::stringlrefstr;// ✅ T 绑定左值// std::string lref2 std::string{};// ❌ T 不能绑定纯右值conststd::stringclrefstd::string{};// ✅ const T万能绑定左值/亡值/纯右值std::stringrrefstd::move(str);// ✅ T绑定亡值// std::string rref2 str; // ❌ T不能绑定普通左值T仅绑定左值const T可绑定所有值类别T非模板仅绑定右值prvalue/xvalue模板中T为转发引用依据实参值类别自动推导左/右值引用1.4 浅拷贝、深拷贝值类别与拷贝行为基础浅拷贝仅复制指针变量多个对象指向同一块堆内存风险双重析构释放内存structBadBuffer{char*buf;BadBuffer(constchar*src){bufnewchar[strlen(src)1];strcpy(buf,src);}// 编译器默认拷贝构造 浅拷贝~BadBuffer(){delete[]buf;}};voiddemo(){BadBuffera(123);BadBuffer ba;// 浅拷贝a.buf与b.buf指向同一堆内存// 函数退出时a、b析构重复delete[] buf → 崩溃}深拷贝新建堆内存独立复制数据每个对象独占资源structDeepBuffer{char*buf;DeepBuffer(constchar*src){bufnewchar[strlen(src)1];strcpy(buf,src);}// 拷贝构造深拷贝DeepBuffer(constDeepBufferother){bufnewchar[strlen(other.buf)1];strcpy(buf,other.buf);}~DeepBuffer(){delete[]buf;}};1.5 值类别控制移动构造触发普通左值优先调用拷贝构造亡值/纯右值优先调用移动构造std::move不移动资源仅仅转换值类别允许编译器匹配移动重载std::string s{demo};std::string s1s;// 左值 → 拷贝构造std::string s2std::move(s);// move转为亡值 → 移动构造// 移动后s处于合法但未指定状态不要再随意访问s内部资源二、移动语义、拷贝消除 三/五/零法则2.1 移动语义核心转移堆资源所有权避免昂贵深拷贝源对象变为有效但可丢弃状态。classBuffer{public:char*data;size_t len;Buffer(size_t n):len(n),data(newchar[n]){}// 移动构造Buffer(Bufferother)noexcept{dataother.data;lenother.len;other.datanullptr;// 源对象置空防止析构释放other.len0;}// 移动赋值Bufferoperator(Bufferother)noexcept{if(thisother)return*this;delete[]data;dataother.data;lenother.len;other.datanullptr;other.len0;return*this;}// 拷贝构造深拷贝Buffer(constBufferother){lenother.len;datanewchar[len];memcpy(data,other.data,len);}~Buffer(){delete[]data;}};voidtest_move(){Bufferb1(1024);Buffer b2std::move(b1);// 移动构造无新堆分配}2.2 拷贝消除 Copy ElisionURVO (RVO) 无名返回值优化返回临时纯右值C17 强制优化NRVO 具名返回值优化返回函数内局部具名对象可选优化// URVO C17强制消除临时std::stringcreate_str_urvo(){returnstd::string(hello world);}// NRVO可选优化std::stringcreate_str_nrvo(){std::strings(hello world);returns;}voiddemo_elision(){std::string screate_str_urvo();// 直接在s的内存上构造对象跳过临时拷贝/移动}注意即使被消除对应构造函数依然要求可访问C17 URVO有部分放宽2.3 三五零法则三法则Rule of ThreeC98如果你手动定义以下任意一个应当显式定义/删除全部3个析构函数拷贝构造函数拷贝赋值运算符背景管理堆内存时默认生成的拷贝是浅拷贝必然引发内存错误// 违反三法则反面示例classBadObj{int*p;public:BadObj(){pnewint;}~BadObj(){deletep;}// 未定义拷贝构造、拷贝赋值编译器生成浅拷贝 → 双重释放风险};五法则Rule of FiveC11扩展在三法则基础上增加移动相关函数定义任意一个建议统一管理5个析构函数拷贝构造拷贝赋值移动构造移动赋值classResource{public:int*ptr;Resource(){ptrnewint{0};}~Resource(){deleteptr;}// 拷贝Resource(constResourceo);Resourceoperator(constResourceo);// 移动Resource(Resourceo)noexcept;Resourceoperator(Resourceo)noexcept;};零法则Rule of Zero现代C推荐最优实践不要手动管理裸资源使用unique_ptr、容器等RAII类型作为成员不自定义析构、拷贝、移动函数依赖编译器自动生成从根源规避内存bug。// 符合零法则classSafeResource{std::unique_ptrintptr;// RAII智能指针接管内存public:SafeResource():ptr(std::make_uniqueint(0)){}// 无需手写析构、拷贝、移动智能指针自动处理生命周期};三、RAII 与资源所有权模型3.1 RAII 资源获取即初始化思想资源在构造时申请析构时自动释放对象生命周期绑定资源生命周期离开作用域自动释放异常场景同样不会泄漏。// 文件RAII简易示例classFileGuard{FILE*fpnullptr;public:FileGuard(constchar*path,constchar*mode){fpfopen(path,mode);}~FileGuard(){if(fp)fclose(fp);}// 禁止拷贝FileGuard(constFileGuard)delete;FileGuardoperator(constFileGuard)delete;};voidread_file(){FileGuardfg(test.txt,r);// 无论正常return、异常抛出离开作用域都会执行析构关闭文件}3.2 三种所有权模型(1独占所有权 std::unique_ptr资源同一时刻仅属于一个智能指针只允许移动禁止拷贝开销最低。voidunique_demo(){autop1std::make_uniqueint(100);// auto p2 p1; ❌ 禁止拷贝autop2std::move(p1);// ✅ 转移独占所有权// p1现在为空不再管理资源}(2共享所有权 std::shared_ptr引用计数管理多个指针共同持有资源计数归零释放存在原子计数开销、循环引用风险。适用场景异步任务、多方模块无法确定谁最后使用资源。voidshared_demo(){autos1std::make_sharedint(200);autos2s1;// 共享引用计数1// s1、s2析构计数递减全部销毁后释放堆内存}(3无所有权借用裸指针T*、引用T仅观察资源不负责释放。规范裸指针只做观察者不管理生命周期voidobserve(int*p){if(p)std::cout*p;}voiddemo(){autoupstd::make_uniqueint(666);observe(up.get());// 借用不转移所有权}3.3 weak_ptr配合shared_ptr不增加引用计数解决循环引用可以尝试提升为shared_ptr判断资源是否存活structNode{intval;std::weak_ptrNodenext;// 使用weak_ptr避免循环引用};3.4 接口设计通过类型显式表达所有权voidfunc1(int*p);// 借用无所有权voidfunc2(std::unique_ptrintp);// 调用方转移独占所有权voidfunc3(std::shared_ptrintp);// 建立共享所有权四、内存编码工程规范非必要不主动new/delete手动管理堆内存优先栈对象、std::vector、std::string等容器// 推荐std::vectorintarr(100);// 不推荐int*arrnewint[100];delete[]arr;需要堆内存优先std::unique_ptr独占首选非必要不使用std::shared_ptr仅生命周期无法静态确定时使用异步、多模块共享裸指针仅用作观察禁止利用裸指针承担内存释放职责遵循Rule of Zero优先依靠RAII封装资源