C++坑系列,C++ std::atomic 拷贝构造函数问题分析与解决方案

问题概述

在实现高并发日志系统时,遇到了一个典型的C++ std::atomic 拷贝构造函数被删除的编译错误:

复制代码
error: use of deleted function 'logrotation::LogStatistics::LogStatistics(const logrotation::LogStatistics&)'
error: use of deleted function 'std::atomic<long unsigned int>::atomic(const std::atomic<long unsigned int>&)'

错误详细分析

错误根本原因

1. std::atomic 的设计原理

std::atomic 类型故意删除了拷贝构造函数和拷贝赋值操作符,这是有意的设计决策:

cpp 复制代码
template<typename T>
class atomic {
public:
    atomic(const atomic&) = delete;            // 拷贝构造函数被删除
    atomic& operator=(const atomic&) = delete; // 拷贝赋值被删除
    
    // 只允许原子操作
    T load() const noexcept;
    void store(T value) noexcept;
    // ...
};
2. 为什么这样设计?

原子性保证:

  • 拷贝操作无法保证原子性
  • 拷贝过程中可能发生数据竞争
  • 多个原子变量之间的拷贝不是原子操作

示例问题:

cpp 复制代码
std::atomic<int> a{10};
std::atomic<int> b{20};

// 假设允许这样做(实际不允许)
a = b;  // 这个操作不是原子的!
// b的值可能在读取和写入a之间被其他线程修改

3. 具体错误位置分析

问题代码:
cpp 复制代码
struct LogStatistics {
    std::atomic<uint64_t> total_logs{0};      // 原子类型
    std::atomic<uint64_t> sampled_logs{0};    // 原子类型
    std::atomic<uint64_t> dropped_logs{0};    // 原子类型
    // ...
};

// 在函数中返回LogStatistics对象时触发错误
LogStatistics get_statistics() const {
    return statistics_;  // ❌ 尝试拷贝包含atomic的结构体
}
编译器错误流程:
  1. get_statistics() 尝试返回 LogStatistics 对象
  2. 返回操作需要调用拷贝构造函数
  3. LogStatistics 包含 std::atomic 成员
  4. std::atomic 的拷贝构造函数被删除
  5. 编译器报错:无法合成默认拷贝构造函数

✅ 解决方案实施

方案1: 自定义拷贝构造函数(推荐)

核心思路 :使用原子操作的 load() 方法安全地读取值,这种方案没有拷贝构造aotmic,只是读取aotmic的值而已

cpp 复制代码
struct LogStatistics {
    std::atomic<uint64_t> total_logs{0};
    std::atomic<uint64_t> sampled_logs{0};
    std::atomic<uint64_t> dropped_logs{0};
    std::unordered_map<LogLevel, uint64_t> level_counts;
    std::chrono::system_clock::time_point start_time;
    
    // 默认构造函数
    LogStatistics() : start_time(std::chrono::system_clock::now()) {}
    
    // ✅ 自定义拷贝构造函数
    LogStatistics(const LogStatistics& other) 
        : total_logs(other.total_logs.load()),      // 原子读取
          sampled_logs(other.sampled_logs.load()),  // 原子读取
          dropped_logs(other.dropped_logs.load()),  // 原子读取
          level_counts(other.level_counts),         // 普通拷贝
          start_time(other.start_time) {}           // 普通拷贝
    
    // ✅ 自定义赋值操作符
    LogStatistics& operator=(const LogStatistics& other) {
        if (this != &other) {
            total_logs = other.total_logs.load();      // 原子写入
            sampled_logs = other.sampled_logs.load();  // 原子写入
            dropped_logs = other.dropped_logs.load();  // 原子写入
            level_counts = other.level_counts;
            start_time = other.start_time;
        }
        return *this;
    }
};

方案2: 移除原子性(不推荐)

cpp 复制代码
// ❌ 不推荐:失去线程安全性
struct LogStatistics {
    uint64_t total_logs = 0;        // 普通类型,非线程安全
    uint64_t sampled_logs = 0;      // 普通类型,非线程安全
    uint64_t dropped_logs = 0;      // 普通类型,非线程安全
    // ...
};

方案3: 使用指针或引用(复杂)

cpp 复制代码
// 返回引用而不是值
const LogStatistics& get_statistics() const {
    return statistics_;  // 返回引用,避免拷贝
}
相关推荐
panxianren28 分钟前
Bun 1.4 的 bun test --parallel 将 4 秒的测试套件缩短到约 1 秒
java·javascript
程序喵大人2 小时前
【C++入门】编译链接模型 - 01 一个 C++ 程序是怎样变成可执行文件的
开发语言·c++·编译链接模型
sunshine22 girl2 小时前
Java学习七 Java 项目实战2-注册业务
java·学习
省钱兄--zs3 小时前
北京24小时自助健身房系统软件开发实战:从架构设计到部署指南
java·数据仓库·spring boot·小程序·需求分析
youdexiang3 小时前
Android录音软件时间关键词检索功能分析
java·人工智能
by209993 小时前
vector 的模拟实现:扩容与边界的详细阐述(下)
c++·经验分享·vector·动态数组
可乐鸡翅yeah_4 小时前
FFmpeg 生成 HLS 独立分片 independent‑segments 参数到底有什么用
javascript·ffmpeg·音视频·safari·m3u8
sunshine22 girl4 小时前
Java学习七 Java 项目实战1-项目创建和主页面搭建
java·开发语言·学习
福兮说4 小时前
canvas 旋转图片的六个坑:四角被裁、Math.ceil 多出 1px 黑边、JPG 角发黑、翻转方向反了
前端·javascript·图像处理·canvas
code2cat4 小时前
Java进阶篇之AtomicStampedReference:引用回到原值,版本戳仍能记住变化
java·cas·并发编程·aba