当前位置: 首页 > news >正文

【C++】std::exchange 原子性 返回值优化RVO

文章目录

  • std::exchange
      • `std::exchange` 的版本引入与底层原理
        • 1. **引入版本**
        • 2. **底层原理**
          • 核心实现(简化版):
          • 典型用法示例:
        • 3. **C++11 之前的替代方案**
        • 4. 经典应用场景对比
          • C++11 风格(推荐):
          • C++98 风格:
        • 5. 性能对比(GCC -O3)
        • 6. 现代 C++ 的演进
      • 关键总结
  • `std::exchange` 和 `std::swap`
      • 示例对比
      • 总结
  • `std::exchange` 的**原子性**
      • 1. **单一对象操作的原子性**
        • 示例(普通类型)
      • 2. **原子类型的原子性保证**
        • 示例(原子类型)
      • 3. **对比普通赋值的非原子性**
      • 4. **注意:原子性≠线程安全**
      • 总结
  • 独占指针初始化问题
    • cpp14 无优化
    • cpp14 优化
    • cpp17 无优化
    • cpp17优化
    • 结论

std::exchange

std::exchange 的版本引入与底层原理

1. 引入版本

std::exchangeC++14 引入的标准库工具函数,定义在 <utility> 头文件中。


2. 底层原理
核心实现(简化版):
template <typename T, typename U = T>
T exchange(T& obj, U&& new_value) {T old_value = std::move(obj);  // 保存旧值obj = std::forward<U>(new_value); // 赋予新值return old_value; // 返回旧值
}

关键机制

  • 移动语义:用 std::move 避免旧值的拷贝
  • 完美转发:用 std::forward 保持新值的值类别(左值/右值)
  • 强异常安全:若赋值抛出异常,旧值仍有效
典型用法示例:
std::vector<int> v1{1, 2, 3};
std::vector<int> v2 = std::exchange(v1, {4, 5, 6});
// 现在:
//   v1 = {4, 5, 6}
//   v2 = {1, 2, 3}

3. C++11 之前的替代方案

在 C++98/03 中,需要手动实现类似功能:

template <typename T, typename U>
T legacy_exchange(T& obj, const U& new_value) {T old_value = obj; // 拷贝构造(无移动语义)obj = new_value;   // 拷贝赋值return old_value;
}

缺陷

  • 效率低:多一次拷贝操作
  • 不支持移动语义
  • 无法完美转发右值

4. 经典应用场景对比
C++11 风格(推荐):
// 安全转移资源所有权
std::unique_ptr<Resource> acquire_resource() {static std::unique_ptr<Resource> res = std::make_unique<Resource>();return std::exchange(res, nullptr); // 原子性转移
}
C++98 风格:
// 需要额外临时变量
std::auto_ptr<Resource> acquire_resource() {static std::auto_ptr<Resource> res(new Resource);std::auto_ptr<Resource> tmp = res; // 拷贝(所有权转移)res.reset(); // 显式置空return tmp;  // 返回临时变量
}

5. 性能对比(GCC -O3)

假设操作 std::string 对象:

操作指令数(x86-64)备注
C++98 手动交换15含冗余拷贝
std::exchange8完全利用移动语义
编译器优化后5部分操作可内联消除

6. 现代 C++ 的演进
  • C++20 增强constexpr 支持(可在编译期使用)
    constexpr int demo() {int x = 10;return std::exchange(x, 20); // 返回10,x变为20
    }
    static_assert(demo() == 10);
    

关键总结

维度C++98C++11(含exchange
资源转移效率需要拷贝移动语义零开销
代码安全性容易遗漏重置操作原子性操作
表达能力需3行代码单函数调用
适用场景基本类型支持移动语义的所有类型

最佳实践:在资源管理(如智能指针、锁等)和状态机实现中优先使用 std::exchange

std::exchangestd::swap

  1. 功能不同

    • std::exchange:将对象替换为新值,并返回旧值,不要求新值的类型与原对象类型相同(只要能转换)。
    • 普通交换(如 std::swap):交换两个对象的值,要求类型完全一致。
  2. 操作对象数量

    • std::exchange:操作单个对象(替换其值)。
    • 普通交换:操作两个对象(互换值)。
  3. 返回值

    • std::exchange:返回旧值(原对象的值)。
    • 普通交换:无返回值(或仅返回引用)。
  4. 应用场景

    • std::exchange:适合需要替换值并保留旧值的场景,如初始化智能指针、重置状态等。
    • 普通交换:适合需要互换两个对象值的场景,如排序算法中的元素交换。

示例对比

std::exchange 的使用

#include <iostream>
#include <utility>int main() {int a = 10;int old_value = std::exchange(a, 20); // a 被替换为 20,返回旧值 10std::cout << "a: " << a << ", old_value: " << old_value << std::endl; // 输出: a: 20, old_value: 10return 0;
}

普通交换的使用

#include <iostream>
#include <utility>int main() {int a = 10;int b = 20;std::swap(a, b); // a 和 b 的值互换std::cout << "a: " << a << ", b: " << b << std::endl; // 输出: a: 20, b: 10return 0;
}

总结

  • std::exchange 是“替换并返回旧值”,适用于需要原子性更新并获取旧值的场景。
  • 普通交换 是“互换两个对象的值”,适用于需要交换两个对象状态的场景。

std::exchange原子性

1. 单一对象操作的原子性

std::exchange 保证对单个对象的修改是不可分割的。具体来说:

  • 替换值的过程不会被中断:当 std::exchange 修改一个对象时,其他线程不会看到“部分修改”的中间状态。
  • 返回旧值与设置新值是一个整体操作:返回的旧值一定是修改前的完整值,新值也一定是一次性设置完成的。
示例(普通类型)
int a = 10;
int old = std::exchange(a, 20); // 原子性替换 a 的值并返回旧值

这里,a 的值要么是 10(替换前),要么是 20(替换后),不会出现中间状态。

2. 原子类型的原子性保证

std::exchange 用于 std::atomic 类型时,它提供硬件级别的原子操作,等价于 std::atomic::exchange

示例(原子类型)
#include <atomic>std::atomic<int> atomic_a(10);
int old = std::exchange(atomic_a, 20); // 原子操作,等价于 atomic_a.exchange(20)

此时:

  • 线程安全:多个线程同时调用 std::exchange 操作同一个原子变量时,不会出现数据竞争。
  • 内存顺序保证:默认使用 std::memory_order_seq_cst(顺序一致性),可通过 std::atomic 的重载版本指定其他内存顺序。

3. 对比普通赋值的非原子性

普通赋值操作不保证原子性,可能被其他线程观察到中间状态:

int a = 10;
int temp = a;  // 步骤1:读取旧值
a = 20;        // 步骤2:写入新值
// 其他线程可能在步骤1和步骤2之间观察到 a 的值

std::exchange 将这两个步骤合并为一个原子操作。

4. 注意:原子性≠线程安全

  • std::exchange单个对象的操作是原子的,但如果多个对象的操作需要原子性(如交换两个变量),仍需额外同步。
  • 对于非原子类型(如 intstd::vector),std::exchange 仅保证操作的不可分割性,但不保证线程安全(多个线程同时访问仍需锁)。

总结

std::exchange 的原子性体现在:

  • 对单个对象的修改不可分割,避免中间状态。
  • std::atomic 类型提供硬件级原子操作,保证线程安全。

这使得它在资源管理(如智能指针)、状态更新(如标志位翻转)等场景中特别有用,能避免竞态条件和数据不一致问题。

独占指针初始化问题

cpp14 无优化

g++ $^ -o $@ -std=c++14 -fno-elide-constructors

// lhr::unique_ptr<int> ptr1 = func1();
lhr::unique_ptr<int> func1()
{return lhr::unique_ptr<int>(new int(520));
}raw cons      // 原始构造,对应 new int(520)
move cons     // 从临时对象移到返回值
delete        // 临时对象析构
move cons     // 返回值移到 ptr1
delete        // 返回值析构
delete        // ptr1 析构// lhr::unique_ptr<int> ptr2 = func2();
lhr::unique_ptr<int> func2()
{ auto p = lhr::unique_ptr<int>(new int(520));return p; 
}
raw cons // 构造p
move cons // p移动到临时对象
delete // p析构
move cons // 临时对象移动返回值
delete //临时对象析构
move cons // 返回值移动 ptr2
delete // 返回值
delete //ptr2

cpp14 优化

g++ $^ -o $@ -std=c++14

// lhr::unique_ptr<int> ptr1 = func1();
lhr::unique_ptr<int> func1()
{return lhr::unique_ptr<int>(new int(520));
}
raw cons //原地构造ptr1
delete// lhr::unique_ptr<int> ptr2 = func2();
lhr::unique_ptr<int> func2()
{ auto p = lhr::unique_ptr<int>(new int(520));return p; 
}
raw cons
delete

cpp17 无优化

g++ my.cc -o main -std=c++17 -fno-elide-constructors

// lhr::unique_ptr<int> ptr1 = func1();
lhr::unique_ptr<int> func1()
{return lhr::unique_ptr<int>(new int(520));
}
raw cons
delete// lhr::unique_ptr<int> ptr2 = func2();
lhr::unique_ptr<int> func2()
{ auto p = lhr::unique_ptr<int>(new int(520));return p; 
}
raw cons
move cons
delete
delete

cpp17优化

g++ my.cc -o main -std=c++17

// lhr::unique_ptr<int> ptr1 = func1();
lhr::unique_ptr<int> func1()
{return lhr::unique_ptr<int>(new int(520));
}
raw cons
delete// lhr::unique_ptr<int> ptr2 = func2();
lhr::unique_ptr<int> func2()
{ auto p = lhr::unique_ptr<int>(new int(520));return p; 
}
raw cons
delete

结论

c++11/14 使用移动构造代替拷贝优化
c++17使用RVO(返回值优化)代替移动构造优化

http://www.dtcms.com/a/286413.html

相关文章:

  • js基本数据类型之字符串类型
  • Python 数据分析与可视化:从基础到进阶的技术实现与优化策略
  • svn如何设置忽略文件夹或者文件的提交
  • PyQt5信号与槽(信号与槽的高级玩法)
  • 四足机器人远程视频与互动控制的全链路方案
  • 【C++】——类和对象(中)——默认成员函数
  • 【世纪龙科技】汽车故障诊断与排除仿真教学软件让课堂更高效安全
  • 【RK3576】【Android14】开发板概述
  • iOS WebView 调试实战 全流程排查接口异常 请求丢失与跨域问题
  • github jekyll+chirpy主题搭建博客
  • 【.net core】支持通过属性名称索引的泛型包装类
  • Spring AI开发智能客服(Tool calling)
  • Linux 定时任务全解析:atd 与 crond 的区别及实战案例(含日志备份 + 时间写入)
  • SpringBoot项目创建,三层架构,分成结构,IOC,DI相关,@Resource与@Autowired的区别
  • 融合优势:SIP 广播对讲联动华为会议 全场景沟通响应提速​
  • 【PHP】Swoole:CentOS安装MySQL+Swoole
  • 强化学习框架VeRL全面解析(架构、调试、修改与应用)
  • vbox增加虚拟磁盘空间大小
  • 基于springboot+vue+mysql的在线文档管理系统的设计与实现(源码+论文+PPT答辩+开题报告)
  • ClickHouse物化视图避坑指南:原理、数据迁移与优化
  • ESP32-IDF LVGL UI 设计工具的使用
  • 海森矩阵(Hessian Matrix)在SLAM图优化和点云配准中的应用介绍
  • Go 的热重载工具 Air 详解
  • 深入理解 Spring:事务管理与事件机制全解析
  • 域名WHOIS信息查询免费API使用指南
  • 【CF】⭐Day104——Codeforces Round 840 (Div. 2) CE (思维 + 分类讨论 | 思维 + 图论 + DP)
  • 【LVGL】Linux LVGL程序几十分钟后UI卡死
  • ubuntu 安装zabbix6 agent2
  • AI进入自动驾驶时代:OpenAI发布革命性ChatGPT Agent
  • 生成式引擎优化(GEO)核心解析:下一代搜索技术的演进与落地策略