左值右值与移动语义

【C++】移动语义、移动构造函数、std::move、移动赋值操作符、左值右值、左值引用右值引用 如果我们知道传入的是一个临时对象的话,我们就不需要担心它们是否活着、是否完整、是否拷贝,我们可以简单的偷用它的资源,给到特定的对象,或者在其他地方使用它们,因为我们知道它是暂时的,它不会存在很长时间,比如上面的ln+fn,就是暂时的,我们就可以从这个临时值中偷取资源,这对优化有很大帮助。左值是带地址的数据,就是有存储支持的变量。右值是临时值,可以用右值引用&&来检测。左值引用只能引用(接受)左值,除非加const,就也可以引用(接受)右值了。是有地址的值(located value),就是左值是有地址的。 阅读详情

左值右值与移动语义

面试题 1:左值和右值的本质区别是什么?

#include <cstring>
int a = 42;       // a 是左值,42 是右值
int* p = &a;      // OK:可以取 a 的地址
// int* q = &42;  // 编译错误:不能取右值的地址

能取地址的是左值(lvalue),不能取地址的是右值(rvalue)。 右值是即将销毁的临时对象,可以安全地"偷"走它的资源。

int x = 10;                // x 是左值
int y = x + 5;             // (x + 5) 是右值,运算结果是一个临时量
int&& rref = 42;           // 右值引用可以绑定右值
// int& ref = 42;          // 编译错误:非常量左值引用不能绑右值
const int& cref = 42;      // OK:const 左值引用可以绑右值(延长生命周期)

面试追问:具名右值引用本身是左值还是右值?

void foo(int&& arg) {       // arg 是 int&& 类型
    // 但 arg 本身是一个具名变量,是左值
    // int&& another = arg; // 编译错误:arg 是左值,不能绑到右值引用
}

面试题 2:std::move 到底做了什么?

template<typename T>
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept {
    return static_cast<std::remove_reference_t<T>&&>(t);
}

std::move 本质就是一个 static_cast<T&&>,不产生任何运行时代码。 它只是告诉编译器"这个对象可以移动",让重载决议选择移动版本的函数。

#include <iostream>
#include <string>
#include <vector>

struct Buffer {
    char* data;
    size_t size;

    Buffer(size_t n) : size(n), data(new char[n]) {
        std::cout << "Constructor\n";
    }

    ~Buffer() { delete[] data; }

    Buffer(const Buffer& other) : size(other.size), data(new char[other.size]) {
        std::memcpy(data, other.data, size);
        std::cout << "Copy constructor\n";
    }

    Buffer(Buffer&& other) noexcept : size(other.size), data(other.data) {
        other.data = nullptr;
        other.size = 0;
        std::cout << "Move constructor\n";
    }
};

int main() {
    Buffer a(1024);
    Buffer b = std::move(a);   // 调用移动构造
    // 此时 a.data == nullptr, a 处于"空"状态
}

移动构造/赋值必须标记 noexcept,否则 std::vector 扩容时会退化回拷贝:

#include <iostream>
#include <vector>

struct MayThrow {
    MayThrow() = default;
    MayThrow(MayThrow&&) { /* 没写 noexcept */ }
};

struct NoThrow {
    NoThrow() = default;
    NoThrow(NoThrow&&) noexcept = default;
};

int main() {
    std::vector<MayThrow> v1;
    v1.reserve(1);
    v1.emplace_back();
    // 再次插入触发扩容:因为移动不保证 noexcept,vector 用拷贝
    v1.emplace_back();  // 调用拷贝而非移动

    std::vector<NoThrow> v2;
    v2.reserve(1);
    v2.emplace_back();
    v2.emplace_back();  // 调用移动,O(1) 指针交换
}

std::vector::reserve 之后扩容时,如果类型提供了 noexcept 移动构造,就用移动(常数时间);否则用拷贝(线性时间)。这是 C++ 标准库的强异常安全保证。


面试题 3:引用折叠四条规则

当模板参数 T 推导为引用类型时,T&& 的行为由引用折叠(reference collapsing)确定:

T 被推导为T&& 实际类型规则
T&T&T& + && → T&
T& &T&T& + & → T&
T&& &T&T&& + & → T&
T&& &&T&&T&& + && → T&&

核心结论:只要出现左值引用(&),结果就是左值引用;只有全是右值引用(&& &&)才得到右值引用。

T& + &&

T&

T& + &

T&

T&& + &

T&

T&& + &&

T&&

template<typename T>
void foo(T&& t) {           // 万能引用(forwarding reference)
    // T 的推导结果决定 t 的类别
}

int main() {
    int x = 0;
    foo(x);     // T = int&  ⇒  T&& = int&   ⇒ 左值引用
    foo(0);     // T = int   ⇒  T&& = int&&  ⇒ 右值引用
}

面试追问:为什么模板中的 T&& 叫万能引用,而写死的 int&& 不是?

答:只有发生在模板参数推导或 auto&& 场景中的 T&& 才是万能引用(前向引用),会根据实参自动折叠。显式写 int&& 永远只是一个右值引用,不能绑定左值。


面试题 4:std::forward 与完美转发原理

完美转发(perfect forwarding)的目标:将参数原样(包括值类别)传递给下一个函数。

#include <iostream>
#include <string>
#include <utility>

void process(int& x)  { std::cout << "Lvalue\n"; }
void process(int&& x) { std::cout << "Rvalue\n"; }

template<typename T>
void relay(T&& t) {
    process(std::forward<T>(t));
}

int main() {
    int a = 0;
    relay(a);   // 输出 Lvalue
    relay(42);  // 输出 Rvalue
}

std::forward 的实现原理:

template<typename T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept {
    return static_cast<T&&>(t);
}

template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&& t) noexcept {
    static_assert(!std::is_lvalue_reference_v<T>);
    return static_cast<T&&>(t);
}

当 T = int& 时:forward<int&> 返回 int& && → 折叠为 int&,左值转发。
当 T = int 时:forward<int> 返回 int&&,右值转发。

对比 std::move 和 std::forward:

函数行为典型场景
move(t)无条件转右值引用明确要转移资源
forward<T>(t)保真传递值类别模板中转发参数给下一层

面试题 5:push_back 与 emplace_back 的区别

#include <iostream>
#include <string>
#include <vector>

struct Expensive {
    std::string name;
    explicit Expensive(std::string s) : name(std::move(s)) {
        std::cout << "构造\n";
    }
    Expensive(const Expensive&) { std::cout << "拷贝\n"; }
    Expensive(Expensive&&) noexcept { std::cout << "移动\n"; }
};

int main() {
    std::vector<Expensive> v;
    v.reserve(10);
    std::cout << "--- push_back 传入临时对象 ---\n";
    v.push_back(Expensive("a"));
    // 构造 + 移动

    std::cout << "--- emplace_back 直接构造 ---\n";
    v.emplace_back("b");
    // 只构造,无移动/拷贝
}

emplace_back 直接在 vector 分配的内存上构造对象,传的是构造参数;push_back 传的是已构造好的对象(需要构造一次 + 移动/拷贝一次)。

C++17 起 push_back 也有右值重载,但 emplace_back 在传入多个参数或不想写临时对象时更优。


面试题 6:RVO / NRVO / Guaranteed Copy Elision

RVO(Return Value Optimization)

编译器在调用方栈帧上直接构造返回值,省略拷贝/移动:

#include <iostream>

struct Big {
    Big() { std::cout << "构造\n"; }
    Big(const Big&) { std::cout << "拷贝\n"; }
    Big(Big&&) noexcept { std::cout << "移动\n"; }
};

Big makeRVO() {
    return Big{};       // 临时对象 —— RVO 消除拷贝
}

int main() {
    std::cout << "--- RVO ---\n";
    Big b = makeRVO();  // 只输出一次"构造"
}

NRVO(Named RVO)

对具名变量做返回值优化,但编译器不强制:

Big makeNRVO() {
    Big local;          // 具名对象
    // 一些操作...
    return local;       // NRVO:编译器尝试省略拷贝/移动
}

C++17 Guaranteed Copy Elision

C++17 起,prvalue(纯右值)不再触发拷贝/移动,语言层面保证省略:

#include <iostream>

struct Guaranteed {
    Guaranteed() { std::cout << "构造\n"; }
    Guaranteed(const Guaranteed&) = delete;   // 拷贝删除
    Guaranteed(Guaranteed&&) = delete;        // 移动也删除
};

int main() {
    Guaranteed g = Guaranteed{};  // C++17 OK!prvalue 直接构造
    // C++14 中不行(需要可拷贝/移动)
}

C++17 guaranteed copy elision 的前提是从 prvalue 初始化。 而 NRVO 不是强制性的,所以即使 C++17 也不能保证 NRVO。


完整速查表

概念一句话总结
左值有地址、有名字,取址 & 合法
右值临时对象、字面量,取址不合法
std::movestatic_cast<T&&>,只标记、不移动
std::forward根据模板参数保真传递值类别
T&&(模板中)万能引用,自动折叠
引用折叠有 & 就 &,只有 && && 才是 &&
move noexcept必须写,否则 vector 扩容退化为拷贝
emplace_back原地构造,零额外移动/拷贝
RVO编译器跳过拷贝,直接构造到返回位置
C++17 保证省略prvalue 初始化零拷贝,语言级承诺
OpenClaw赚钱实录:从“养龙虾“到可持续变现的实践指南 OpenClaw已经开源3个月有余,作为深耕AI Agent领域的博主,我亲身探索了各种基于OpenClaw的赚钱方法,也踩过坑、付出了时间与精力。现在,我将这些真金白银砸出来的实战经验,毫无保留地总结成这个专栏,分享给不想从零开始摸索的你。博主在优快云已有7万+粉丝和1100万+阅读量,但这却是我的第一个付费专栏。正因如此,我比任何人都更珍惜这个“第一次”。我会像一个手艺人打磨作品一样,死磕每一篇文章的质量,并持续根据订阅者的反馈优化内容。也许你的疑问就是当篇文章的优化点。 阅读详情

相关推荐

深度理解嵌入(embedding)层

首先从one_hot开始: one_hot的优点:计算方便快捷、表达能力强。 one_hot的缺点:过于稀疏,过于占用资源,尤其是在数据量特别大时;数据之间完全独立。

weixin_44441131的博客 5658

【C++移动语义与完美转发】左值右值,引用,引用折叠,移动语义,万能引用与完美转发

本节我们讲解了C++左值右值,引用,引用折叠,移动语义,万能引用与完美转发

m0_73800387的博客 1532

STM32G474实战:数字LLC电源控制程序从零到跑通(附完整代码解析)

本文详细介绍了基于STM32G474微控制器开发数字LLC电源控制程序的完整实战流程。从硬件平台搭建、CubeMX工程配置,到核心控制逻辑、双速率中断框架、数字PID实现,再到Burst模式、状态机等高级策略与异常处理,提供了从零到跑通的系统化指南与代码解析,助力开发者掌握高效数字电源的程序编写与调试技巧。

s1t2u3的博客 1238

C++ 的左值右值和移动语义到底是什么?

相信很多人已经在无数地方看过无数关于左值右值的解释。看完之后面对一堆概念,甚至有些文章提出的公式僵化记忆点,很多人是一脸懵逼。我来提供一个比较简化的理解思路。所谓左值,是一个有可以操作的内存的变量。int a = 1;//栈上有内存地址//堆上有内存地址//静态变量区上有内存地址而相应的右值,则是两部分组成(个人分类)。一是字面量,类似 “立即数” ,编译后会变成指令区(代码区)的一个参数,而不需要进行额外存储,不存在内存地址;

weixin_42445065的博客 863

CF鼠标宏防封指南:手把手教你调校罗技G HUB压枪脚本的延迟与随机数

本文详细解析了如何通过罗技G HUB的Lua脚本功能调校鼠标宏,实现拟真操作体验。重点介绍了随机数应用、动态后坐力补偿和智能操作序列等高级技术,帮助玩家在不违反游戏规则的前提下提升操作精度。

weixin_26841957的博客 334

YOLOv13安装教程 手把手教你融合空间注意力机制(SAB)的RAB卷积模块 自动“聚焦”重要的特征 map指标提升8.96,准确率提升3.56

本文提出了一种改进的YOLOv13目标检测模型,通过引入RAB(残差注意力模块)显著提升了检测性能。实验结果显示,添加RAB模块后,mAP指标提升了8.96,准确率提高了3.56%。文章详细介绍了RAB模块的实现方法:1)采用多级残差连接增强特征提取能力;2)结合空间注意力机制(SAB)自动聚焦关键区域;3)通过1×1卷积实现通道维度压缩与恢复。同时提供了完整的模型移植方案,包括配置文件修改、任务处理代码调整以及模块实现细节,为YOLO系列模型的性能优化提供了有效参考。

FJN110的博客 646

【C++11】(1)从左值右值到移动语义

本文介绍了C++11中的两项重要特性:列表初始化和右值引用。列表初始化实现了统一的初始化方式,通过std::initializer_list支持容器初始化。右值引用引入了移动语义,通过移动构造和移动赋值减少拷贝开销,提升性能。文章详细解析了左值/右值的区别、引用折叠规则和完美转发实现机制,展示了C++11如何通过右值引用优化资源管理。这些特性显著提升了C++的表达能力和运行效率,是C++11最重要的改进之一。

m0_65245374的博客 757

ISP算法:自动白平衡(AWB)

1. 为什么要做白平衡? 人类视觉系统具有颜色恒常性特点,一次人类对物体观察不受光源影响。但是Sensor在不同光线下,物体呈现的颜色不同,在晴朗天空下会偏蓝,在烛光下会偏红。如图: 2.什么是自动白平衡? 为了消除光源对于图像传感器成像的影响,模拟人类视觉系统的颜色恒常性。保证在任何场景下看到的白色是真正的白色。 3. 如何做自动白平衡? 只要乘以一个对角矩阵,便可以进行白平衡...

a200800170331的专栏 1万+

左值右值完美转发移动语义

与右值引用相关的表达式,例如:将要被移动的对象,T&&函数返回的值,std::move的返回值,转换成T&&的类型的转换函数的返回值。深拷贝将对象中的堆区资源复制了一份,造成资源的申请和释放,如果直接使用源对象使用的资源,可以节省资源申请和浪费的时间。在函数模板中,可以将参数完美的转发给其他函数,不仅仅保持参数的值,而且保证参数的左,右值属性不变。1) 如果模板中的参数写成T&&,既可以接收左值引用,又可以接受右值引用。C++11扩展了右值的概念,将右值分为了纯右值和将亡值;能对表达式取地址,就是左值;

wjfdsklfdkfksd的博客 430

突破限制:巧用Azure OpenAI,畅玩Gemini模型

通过 Azure OpenAI 服务这一巧妙的途径,间接调用 Gemini 模型,打破网络限制的壁垒。本文将详细介绍这一方法,无论你是人工智能领域的新手,还是经验丰富的开发者,都能从中获取实用的技巧,轻松开启 Gemini 模型的探索之旅

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 181

鸿蒙录屏推流应用完整解决方案

本文介绍了鸿蒙录屏推流应用的完整解决方案。项目采用分层架构设计,包含录屏、编码、推流等功能模块,要求实现4小时稳定运行且延迟小于3秒。技术实现包括:1) 通过config.json配置应用权限和功能;2) 使用ScreenRecorderManager类管理录屏服务,实现视频采集、音频录制和编码参数配置;3) 核心功能包含初始化录屏器、开始/停止录屏等操作。该方案充分利用鸿蒙系统的多媒体API,实现了高性能的屏幕录制与实时推流功能。

huanghm88的专栏 289

C++ 移动语义 (左值右值 移动构造/赋值函数 move强制移动)

什么是移动语义 移动语义是C++11新增的特性,利用移动语义可以做到移动一个对象而不是拷贝对象。 一般用于处理临时对象的赋值,例如: vector<int> f(const vector<int> &arr) { vector<int> tmp; for(int i=0;i<arr.size();i++) tmp.push_back(arr[i]+233); } ...

夢裡夢到醒不來的夢 489

Transformer实战(1)——词嵌入技术详解

词嵌入的核心思想是,通过将词语表示为稠密的向量,使得计算机可以更有效地处理和理解文本中的语义关系。在本节中,我们学习了单词分布式表示的概念及其实现,包括静态嵌入和动态嵌入,实现了 Word2Vec 和 GloVe 模型,并介绍了如何使用 Gensim API 探索嵌入空间。

盼小辉丶的博客 1万+

C++之移动语义与左值右值深入学习:从入门到精通!

综上所述,我们所谓的移动语义呢其实指的就是类里面所具有的两个函数:一个叫做移动构造函数,一个叫做移动赋值运算符函数。这样当我们传递进来的是右值的时候我们就不必要再去执行深拷贝而只要去执行浅拷贝即可。

qq_53324833的博客 1102

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

左值右值 右值引用 移动语义 完美转发

  一、左值与右值   左值 与 右值是C++中表达式的属性,在C++11中,每个表达式有两个属性:类型(type,除去引用特性,用于类型检查)和值类型(value category,用于语法检查,比如一个表达式结果是否能被赋值)。   值类型包括3个基本类型:lvalue、prvalue与xrvalue,后两者又统称为rvalue,lvalue我们称为左值,可以将左值看成是一个可以获取地址...

金色的省略号 281

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

C++11:列表初始化,理解左值右值, 右值引用和移动语义,类型分类

C++11:列表初始化,理解左值右值, 右值引用和移动语义,类型分类

2401_88433210的博客 722

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

现代C++⊂C++11篇(二)左值右值、移动语义与完美转发全解析

本文围绕C++11核心的移动语义体系展开,从左值、右值的基础概念与核心判断标准入手,详解左右值引用的绑定规则、底层实现与函数重载匹配逻辑。深入剖析移动语义的诞生背景、移动构造与移动赋值的实现原理,结合编译器返回值优化辨析其真实性能价值。同时拆解值分类细节、引用折叠规则与完美转发的底层机制,帮助读者从语法到对象模型系统掌握现代C++的资源复用与泛型转发特性,扫清认知误区。

沫漓的博客 517

左值?右值?&&是什么鬼?—— 写给所有被C++移动语义折磨的人

左值?右值?&&是什么鬼?—— 写给所有被C++移动语义折磨的人

Henry313的博客 807

C++面试基础知识:移动语义 & Perfect Forwarding

C++左值右值问题,以及移动语义+完美转发问题,All in one

qq_45983373的博客 1239

C++系列07:左值右值,内存分区,类的六大默认函数,浅拷贝深拷贝,移动语义,append函数

对应函数,编译器会自动合成;一旦手动写任意一个,部分默认函数不再生成。

2301_81587395的博客 296

C++八股——类型推导

左值、右值、左值引用、右值引用、auto、decltype、result_of、invoke_result

m0_46329175的博客 1012

C++11新特性—nullptr,bind,lambda函数,智能指针,左值右值,移动语义与完美转发

NULL一般来自C语言,是宏定义,在C++语言中,NULL被定义为整数0,但是这样会无法与整数0区分,所以C++11引入新特性nullptr,可以区分整数与指针特性,但是仍然存在问题-不能区分指针类型,这种情况下,必须显示的指明参数类型。

qaaaaaaz的博客 1215

C++:右值引用与移动语义(C++11知识点——上)

左值:可以取地址,可以出现在赋值操作符的左端和右端,通常是变量和解引用的指针,其具有持久性——所有可以定位的值(可以进行取地址操作)右值:不可以取地址,只能出现在赋值操作符的右端,通常是常量、临时对象(类型转换的中间值,表达式计算结果)、匿名对象,通常其生命周期很短——指那些不持有对象身份的值。左值的缩写lv,右值的缩写为rv,现在很多人都会为lv解读为 located value(有位置的值),将rv解读为 read value(只能读的值,反而言之不能寻其地址)。由此可见。

FSRMWK的博客 1059

C++高级特性解析:RTTI、移动语义与性能优化

摘要: 本文深入解析现代C++的核心机制,包括运行时类型识别(RTTI)、左值/右值引用、移动语义及性能优化技巧。RTTI依赖虚函数表实现类型识别与安全转型;左值/右值引用语义是资源高效管理的基础,需注意绑定规则;移动语义通过std::move转换所有权,避免冗余拷贝;性能优化涉及临时对象消除、隐式转换控制及编译器合成的移动操作规则。最后强调五法则、完美转发及容器中的移动应用,提出优先使用常量引用、移动语义等实践建议,以提升程序效率与资源利用率。

AlgoCraft 1052

C++左值右值

C++左值右值

m0_60949958的博客 772

关于C++左值与右值——移动构造——完美转发——emplace_back与push_back的逻辑链条详解

左值是有名字、持久存在的对象,它们可以被多次引用。右值是临时的、一次性的对象,通常是表达式的结果。拷贝是创建一个对象的副本,资源会被复制。移动是将一个对象的资源转移到另一个对象,避免不必要的资源复制。右值引用T&&)与移动语义的结合使得 C++ 能够高效地管理临时对象和资源,减少拷贝开销。理解这些概念对于编写高效的 C++ 代码,特别是处理资源密集型操作时至关重要。是 C++ 标准库中和其他容器(如std::liststd::deque等)提供的一个成员函数。它的作用是将一个新元素直接构造。

tjujackson的博客 1244
上一篇: 类型转换四兄弟
下一篇: 模板基础与 SFINAE
Ricky_Theseus
博客等级 码龄3年 126粉丝 158原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ricky_Theseus

感谢大家,祝您生活愉快

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值