You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++对象创建最佳实践:智能指针高效安全使用及并发场景咨询

关于C++智能指针高效安全使用的最佳实践与你的代码分析

首先给你吃个定心丸:你这段代码的核心逻辑(用std::shared_ptr管理对象生命周期、引用其他关联对象)是安全的,但针对你数亿级对象+并发的场景,还有不少可以优化和调整的地方,下面我逐点拆解:

一、当前代码的安全性与小问题

  1. std::make_shared的使用是正确的
    你用make_shared创建shared_ptr的方式非常规范,它不仅避免了裸指针的风险,还能一次性分配对象内存和引用计数控制块,比手动new后封装shared_ptr更高效,也更安全。

  2. makePet成员函数的不合理性
    当前makePet是普通成员函数,意味着你必须先构造一个Pet实例才能调用它创建新的Pet——这显然是个逻辑矛盾(第一个Pet怎么来?)。建议把它改成静态成员函数,这样无需提前实例化Pet就能创建对象:

    static std::shared_ptr<Pet> makePet(const int id, const std::shared_ptr<Property>& myProperty, const std::shared_ptr<Property>& myProperty2) {
        return std::make_shared<Pet>(id, myProperty, myProperty2);
    }
    

    另外,参数改成const std::shared_ptr<Property>&可以避免不必要的shared_ptr复制(减少原子引用计数操作的开销),这点对高并发场景很重要。

  3. 循环引用的隐藏风险
    你的relatives用std::shared_ptr<Pet>存储其他Pet对象,如果出现A的relatives包含B,B的relatives又包含A的情况,就会形成循环引用——两个对象的引用计数永远不会降到0,导致内存泄漏。针对这个场景,正确的做法是把relatives的类型改成std::map<double, std::weak_ptr<Pet>>:

    • weak_ptr不会增加引用计数,只是观察对象的生命周期
    • 需要访问亲属对象时,调用weak_ptr::lock()获取shared_ptr,如果对象已被销毁,lock()会返回空指针,安全可靠

二、数亿级对象场景的性能优化

针对你要创建数亿级对象的需求,shared_ptr的默认使用方式可能不够高效,这里有几个关键优化点:

  1. 减少原子引用计数操作的开销
    shared_ptr的引用计数修改是原子操作,虽然线程安全,但高频率的复制会带来性能损耗。如果某个对象在创建后,先在单线程环境中使用,之后才进入共享环境,可以先用std::unique_ptr管理,最后再转成shared_ptr,减少原子操作的次数:

    // 单线程内创建并处理
    auto tempPet = std::make_unique<Pet>(id, prop1, prop2);
    // 处理完成后转为shared_ptr交给共享环境
    auto sharedPet = std::move(tempPet);
    
  2. 替换std::map为更高效的容器
    std::map是红黑树实现,插入/查找都是O(log n)复杂度,数亿级数据下这个开销会非常大。建议:

    • 如果键的哈希特性良好(比如double是整数转换而来,没有精度问题),换成std::unordered_map(平均O(1)复杂度)
    • 如果是读多写少的场景,还可以考虑std::flat_hash_map(第三方实现,比如abseil或folly库),内存连续性更好,缓存命中率更高
  3. 使用自定义内存池优化分配
    数亿级对象的内存分配会成为核心瓶颈,默认的内存分配器(malloc/free)在高频分配时效率很低。可以:

    • 使用C++17的std::pmr::polymorphic_allocator结合内存池,批量分配内存
    • 自己实现简单的内存池(比如针对Pet和Property的固定大小内存块分配),减少分配/释放的系统调用开销

三、并发访问的处理

你提到要支持多线程访问relatives,这里需要注意两个核心点:

  1. 容器的线程安全
    std::map和std::unordered_map都不是线程安全的,多线程同时读写会导致数据竞争(未定义行为)。解决方式:

    • 最简单的方案:给relatives加std::mutex,每次访问前用std::lock_guard加锁:
      std::mutex mtx_;
      std::map<double, std::weak_ptr<Pet>> relatives;
      
      void addRelative(double key, const std::shared_ptr<Pet>& pet) {
          std::lock_guard<std::mutex> lock(mtx_);
          relatives[key] = pet;
      }
      
    • 高并发场景优化:用读写锁std::shared_mutex(C++17及以上),允许多个读线程同时访问,只有写线程需要独占锁,适合读多写少的场景;或者用分段锁(把容器分成多个片段,每个片段一个锁)降低锁竞争。
  2. 对象创建的并发安全
    多个线程同时调用makePet创建Pet是安全的:

    • std::make_shared的内存分配是线程安全的(标准分配器保证)
    • Pet的构造函数中,id_、myProperty_都是初始化后只读的,不存在竞态条件
    • Property的id如果是const成员(建议改成const int id),构造完成后多线程只读访问也是安全的

优化后的简化示例代码

class Property { 
public: 
    const int id; 
    explicit Property(int id) : id(id) {}
};

class Pet {
    const int id_;
    const std::shared_ptr<Property> myProperty_;
    const std::shared_ptr<Property> myProperty2_;
    std::map<double, std::weak_ptr<Pet>> relatives; 
    mutable std::shared_mutex mtx_; // 读写锁,支持多读单写

    // 私有构造函数,强制通过工厂方法创建
    Pet(int id, const std::shared_ptr<Property>& prop1, const std::shared_ptr<Property>& prop2) 
        : id_(id), myProperty_(prop1), myProperty2_(prop2) {}

public:
    // 静态工厂方法
    static std::shared_ptr<Pet> makePet(int id, const std::shared_ptr<Property>& prop1, const std::shared_ptr<Property>& prop2) {
        return std::make_shared<Pet>(id, prop1, prop2);
    }

    // 添加亲属(写操作,独占锁)
    void addRelative(double key, const std::shared_ptr<Pet>& pet) {
        std::lock_guard<std::shared_mutex> lock(mtx_);
        relatives[key] = pet;
    }

    // 获取亲属(读操作,共享锁)
    std::shared_ptr<Pet> getRelative(double key) const {
        std::shared_lock<std::shared_mutex> lock(mtx_);
        auto it = relatives.find(key);
        if (it != relatives.end()) {
            return it->second.lock();
        }
        return nullptr;
    }
};

内容的提问来源于stack exchange,提问作者Markstar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:07:32