普通视图

发现新文章,点击刷新页面。
昨天以前首页

C++的左值/lvalue, 右值/rvalue和右值引用/rvalue references


C++ 左值(lvalue)、右值(rvalue)与右值引用(rvalue reference)

理解 C++ 中的左值、右值及其引用形式,是掌握现代 C++(尤其是 C++11 以后的移动语义/move和完美转发/perfect forwarding)必不可少的基础

📌 什么是左值(lvalue)

左值指的是有名字、可寻址的对象,通常可以出现在赋值语句的左侧。

int x = 10;
x = 20;        // x 是左值
int* p = &x;   // 可以取地址

📌 什么是右值(rvalue)

右值是临时对象,不能被取地址,通常是表达式的结果或字面值常量。

int x = 10;
int y = x + 5;  // x + 5 是右值
y = 100;        // 100 是右值

右值不能出现在赋值语句左侧,且生命周期通常较短。

📌 右值引用(rvalue reference)

C++ 11 引入了右值引用(通过 && 语法),允许我们“捕获”右值。这为移动语义提供了基础。

void process(int& x);   // 左值引用
void process(int&& x);  // 右值引用

int main() {
    int a = 42;
    process(a);        // 调用 int&
    process(10);       // 调用 int&&
}

右值引用通常与移动构造函数、移动赋值运算符std::move 和完美转发/forwarding一起使用。

📊 左值 vs 右值 比较

特性 左值(lvalue) 右值(rvalue)
是否有名称 通常没有
是否可取地址 可以 不可以
是否可出现在赋值语句左侧 可以 不可以
生命周期 受作用域控制 通常是临时的
是否可绑定到 &&

🧪 std::move 与移动语义

std::string a = "hello";
std::string b = std::move(a);  // a 被“移”给了 b

std::move 并不移动对象,它只是把左值强制转换为右值,从而触发移动构造函数或移动赋值运算符。

💡 使用建议

  • 使用左值引用(&
    • 当你需要访问和修改已有变量
    • 不涉及资源转移
  • 使用右值引用(&&
    • 希望接管临时对象的资源
    • 编写移动构造函数或移动赋值
    • 优化性能,避免深拷贝

总结

左值和右值是 C++ 表达式语义的核心。右值引用是现代 C++ 的重要特性,能显著提高资源管理和性能表现。理解它们的区别和用法,是成为高阶 C++ 程序员的基础。

C/C++编程

英文:C++ Lvalue, Rvalue and Rvalue References

本文一共 541 个汉字, 你数一下对不对.
C++的左值/lvalue, 右值/rvalue和右值引用/rvalue references. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c C++的左值/lvalue, 右值/rvalue和右值引用/rvalue references C++ C++ 学习笔记 折腾 程序设计 编程 计算机
The post C++的左值/lvalue, 右值/rvalue和右值引用/rvalue references first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. C++中的 const和constexpr 比较 C++ const 与 constexpr:真正的区别是什么? 一眼看都是定义常量。 为什么这很重要 现代 C++ 鼓励编写不可变、高效且表达力强的代码。两个关键字—const 和 constexpr—是这一理念的核心。它们看起来很相似,但理解它们的不同语义,对于正确利用编译期与运行期行为至关重要。 高层次对比 特性 const constexpr...
  2. 简易教程: C++的智能指针 C++ 智能指针教程 C++ 中的智能指针提供了自动且安全的内存管理。它们通过 RAII(资源获取即初始化)机制,帮助开发者避免内存泄漏和悬空指针的问题,确保对象在生命周期结束时被正确释放。 本教程将介绍 C++ 中三种主要的智能指针: std::unique_ptr:独占式所有权 std::shared_ptr:共享式所有权 std::weak_ptr:非拥有式弱引用 1. std::unique_ptr unique_ptr 拥有独占所有权。一个资源只能被一个...
  3. 被动收入之: 微博红包 今年开始重新经营我的微博帐号 drlai 收到两笔微信红包,应该是来自于官方的支持,150元(成功提现到支付宝)。虽然这不能持久,也没多少,但毕竟实现了零的突破,意义重大。 如果流量上来,内容创作者可能会接受到比较多的赞赏,这也是一个比较简单的变现方法。这也能作为一种被动收入,不过如果不是头部网红,可能杯水车薪,但如果你有好几个类似这样的,也能积少成多! 在用户中心,微博用户可以每天登陆手机微博APP打卡,获取点数和少量的红包钱(几分钱),积少成多! 微博做些小任务可获得积分和几分钱。聊胜于无。 微博的主要盈利模式 微博的主要盈利模式主要包括以下几个方面: 广告收入:微博的大部分收入来源于广告,尤其是品牌广告和效果广告。广告形式包括信息流广告(类似于推文广告)、热门话题广告、开屏广告和视频广告。品牌和企业可以利用微博庞大的用户群和社交互动来提升曝光率、推广品牌和产品。 会员服务:微博提供的VIP会员服务,用户可以支付订阅费用来享受更多的特权,比如个性化的主题、特有的表情包、私密权限设置等。这些会员服务主要面向个人用户,提升其社交体验。 直播和打赏:微博提供直播平台,用户可以通过购买虚拟礼物来支持主播,微博会从这些打赏中抽取一定比例的分成。此外,微博与内容创作者分成,通过内容付费、知识付费等形式变现。 增值服务:针对企业和大V(拥有大量粉丝的用户),微博还提供增值服务,如账号认证、粉丝数据分析、精准推送、推广和营销工具等。这些服务帮助企业提升营销效果,同时也增加了微博的收入来源。 电商和导流:微博上有大量的电商导流业务,尤其是和明星、网红的合作推广。微博用户在浏览社交内容时,可以直接跳转到商品购买链接,微博通过这种方式赚取导流佣金。 游戏联运:微博也会与一些游戏公司合作推出联合运营的游戏,微博负责推广和流量引入,用户充值或付费时,微博可以获得一部分的分成。 这些模式相结合,使得微博能够在广告市场、内容创作和电商等多个领域获利。...
  4. 借助AI快速开源了三个小工具: 写代码越来越像做产品了, AI 真把我宠坏了(Vibe Coding) 程序员的未来?Vibe Coding + AI 一起上! 借助 AI 快速开源了三个小工具 最近,我利用 ChatGPT-4o 和 o4-mini 快速开发并开源了三个小工具。起因其实很简单——每次想转换 YAML/JSON 或进行...
  5. 换了个奥迪Q5大灯花了我1000英镑 我那辆奥迪Q5 SUV今年年检没通过,原因是左前车灯坏了,需要更换。车厂告诉我,光是订购零件就要700多英镑,加上人工费,总费用得1000英镑。但没办法,如果不修,车辆年检(MOT)就过不了,车也不能上路。 MOT是英国的机动车强制性安全检测(Ministry of Transport Test)的简称。 近侧前位置灯不工作 drl/位置灯集成(4.2.1(a)(ii)) Nearside Front Position lamp not working drl/position...
  6. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  7. 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
  8. 通过脚本让电脑实现模拟按键(一直在线功能): VBScript/JScript/PowerShell 本文详细讲诉了用脚本实现定时模拟计算机按键,用于保持系统一直在线的状态。脚本可以使用VBScript、JScript和Powershell。这三种脚本语言都是微软/Microsoft的,不过前两种已经要被淘汰了。 在 Windows 环境下,这三种脚本(PowerShell、VBScript 和 JScript)都通过 CreateObject 来调用 COM 对象 WScript.Shell,以实现模拟按键的功能。例如,可以每隔一分钟模拟按下 Scroll Lock 键一次,从而保持“在线”状态(Keep Alive)。Scroll...

std::thread()使用lambda表达式调用函数导致的离奇bug

作者 大致
2025年1月13日 10:44

最近项目升级开发环境,从visual stdio 2017升级到visual stdio 2022,出现奇怪的现象:同样的代码,2017编译出来风平浪静,2022编译出来一运行就是段错误。

我们的函数大概长这样:

class a {
    public:
    void DoThings(std::string str1, std::string str2, int idx){...};
    void Do0(){...};
    void OnInit() {
        std::string str1 = "FileName.txt";
        std::string str2 = "X:\\Dest\\Path\";
        int i = 0;
        std::thread thd = std::thread([&]{DoThings(str1, str2, i);});
        thd.detach();
    };
};

从debug表现来看,是调用线程函数的时候,传入了典型的野指针。但是啊,三个参数,两个是std::string,另外一个是int啊!string有问题可以理解,可int怎么还能错呢?
把参数改成传入前new,调用后delete,自然是解决了。但心里各种不爽,new一个int,脸往哪搁啊!
好在问题定位的范围比较小,只是起线程调用函数这一小块地方。

2017只支持到C++ 11,而2022是C++ 14,看来问题出在这里了。
去找lambda的说明:

For the entities that are captured by reference (with the capture-default [&] or when using the character &, e.g. [&a, &b, &c]), it is unspecified if additional data members are declared in the closure type, but any such additional members must satisfy.

人家说了,你用lambda进行引用捕获的时候,必须保证捕获的成员是安全的。
看到这里差不多明白了,是[&]的锅。[&]的意思是所有参数按照引用的方式捕获。而你的三个变量都是临时变量,传个毛线的引用啊!
如果不安全会怎么样?这玩意儿叫“未定义的行为”,爱咋样咋样。也就是说,我们的写法触发了这种未定义的右值引用行为,人家可以给你实装成保留地址,也可以转换成另外的指针进行实装。故而2017和2022都没错,错的是写代码的人。
继续写个例子验证一下:

#include "stdafx.h"
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

std::mutex g_mtx;

class CTester {
public:
    CTester() {
    };

    virtual ~CTester() {
    };
    void Run() {
        for (int i = 100, j = 1, n = 0; n < 3; i += 100, j += 1, n++) {
            std::thread thd = std::thread([&#038;] { Show(i, j, "std::thread([&#038;] { Show(i, j); }): "); });
            thd.detach();
            thd = std::thread([&#038;, i, j] { Show(i, j, "std::thread([&#038;, i, j] { Show(i, j); }): "); });
            thd.detach();
            thd = std::thread([&#038;, j] { Show(i, j, "std::thread([&#038;, j] { Show(i, j); }): "); });
            thd.detach();
            thd = std::thread([=] { Show(i, j, "std::thread([=] { Show(i, j); }): "); });
            thd.detach();
        }
    }
    void Show(int x, int y, const char* pri) {
        std::lock_guard< std::mutex>lock(g_mtx);
        char szOut[128] = { 0 };
        sprintf(szOut, "pri = %s x=%d y=%d\n", pri, x, y);
        std::cout << szOut;
    }
};

int main()
{
    std::cout << "Test Start" << '\n';
    CTester t;
    t.Run();
    _sleep(500);
    std::cout << "Test End" << '\n';
    int c = getchar();
    return 0;
}

2017的运行结果:
Test Start
pri = std::thread([&] { Show(i, j); }): x=100 y=1
pri = std::thread([&, i, j] { Show(i, j); }): x=100 y=1
pri = std::thread([&, j] { Show(i, j); }): x=100 y=1
pri = std::thread([=] { Show(i, j); }): x=100 y=1
pri = std::thread([&] { Show(i, j); }): x=200 y=2
pri = std::thread([&, i, j] { Show(i, j); }): x=200 y=2
pri = std::thread([&, j] { Show(i, j); }): x=200 y=2
pri = std::thread([=] { Show(i, j); }): x=200 y=2
pri = std::thread([&] { Show(i, j); }): x=300 y=3
pri = std::thread([&, i, j] { Show(i, j); }): x=300 y=3
pri = std::thread([&, j] { Show(i, j); }): x=300 y=3
pri = std::thread([=] { Show(i, j); }): x=300 y=3
Test End

2022的运行结果:
Test Start
pri = std::thread([&] { Show(i, j); }): x=7599872 y=1992189472
pri = std::thread([=] { Show(i, j); }): x=100 y=1
pri = std::thread([&] { Show(i, j); }): x=7599872 y=1992189472
pri = std::thread([&, i, j] { Show(i, j); }): x=100 y=1
pri = std::thread([&, j] { Show(i, j); }): x=7599872 y=1
pri = std::thread([&, i, j] { Show(i, j); }): x=200 y=2
pri = std::thread([&, j] { Show(i, j); }): x=7599872 y=2
pri = std::thread([=] { Show(i, j); }): x=200 y=2
pri = std::thread([&] { Show(i, j); }): x=7599872 y=1992189472
pri = std::thread([&, j] { Show(i, j); }): x=7599872 y=3
pri = std::thread([&, i, j] { Show(i, j); }): x=300 y=3
pri = std::thread([=] { Show(i, j); }): x=300 y=3
Test End

很明显,只要敢给2022(C++ 14)传引用,它就敢给你乱引……

这部分代码是从厂商的例子里抄的。人家写的是

std::thread([&]{Do0();});

只是隐式地捕获一个this!当然写[&]就行了。
友军抄的时候根本不知道方括号是干什么的,只改了后面,才造成了这样的后果。

那么怎么解决呢?写[=]或者[&, str1, str2, i]吗?并不是。
lambda叫啥?“匿名函数”啊!你都要调用真正的函数了,就别整匿名函数那一套了。
正确的std::thread调用类函数的时候应该长这样:

std::thread thrd = std::thread(&a::DoThings, this, str1, str2, i);
thrd.detach();

我是真心的不喜欢lambda。


  • (1):农历每年冬至所在的月固定为十一月。如果两个冬至间有13次朔望,则该年产生闰月。13个月中,第一个没有二十四节气里的偶数节气的月份就是闰月。因为十一月是起调点,所以农历十二月和正月是不会出现闰月的,正月初一和第一个节气春分受的影响也很小。我数学不好,但查表得到的结果,从1950年到2069的120年间,闰年就是双春,双春就是闰年,一一对应,无一例外。闰月是典型的用太阳历调整月历的规则,所以农历是阴阳历而不是纯阴(月)历。
  • (2):白名单上的微软网址是升级补丁用的
  • (3):统计表里记作4部是因为有同名,公式如何修改还没想好
❌
❌