C++对象创建最佳实践:智能指针高效安全使用及并发场景咨询
首先给你吃个定心丸:你这段代码的核心逻辑(用std::shared_ptr管理对象生命周期、引用其他关联对象)是安全的,但针对你数亿级对象+并发的场景,还有不少可以优化和调整的地方,下面我逐点拆解:
一、当前代码的安全性与小问题
std::make_shared的使用是正确的
你用make_shared创建shared_ptr的方式非常规范,它不仅避免了裸指针的风险,还能一次性分配对象内存和引用计数控制块,比手动new后封装shared_ptr更高效,也更安全。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复制(减少原子引用计数操作的开销),这点对高并发场景很重要。循环引用的隐藏风险
你的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的默认使用方式可能不够高效,这里有几个关键优化点:
减少原子引用计数操作的开销
shared_ptr的引用计数修改是原子操作,虽然线程安全,但高频率的复制会带来性能损耗。如果某个对象在创建后,先在单线程环境中使用,之后才进入共享环境,可以先用std::unique_ptr管理,最后再转成shared_ptr,减少原子操作的次数:// 单线程内创建并处理 auto tempPet = std::make_unique<Pet>(id, prop1, prop2); // 处理完成后转为shared_ptr交给共享环境 auto sharedPet = std::move(tempPet);替换
std::map为更高效的容器std::map是红黑树实现,插入/查找都是O(log n)复杂度,数亿级数据下这个开销会非常大。建议:- 如果键的哈希特性良好(比如
double是整数转换而来,没有精度问题),换成std::unordered_map(平均O(1)复杂度) - 如果是读多写少的场景,还可以考虑
std::flat_hash_map(第三方实现,比如abseil或folly库),内存连续性更好,缓存命中率更高
- 如果键的哈希特性良好(比如
使用自定义内存池优化分配
数亿级对象的内存分配会成为核心瓶颈,默认的内存分配器(malloc/free)在高频分配时效率很低。可以:- 使用C++17的
std::pmr::polymorphic_allocator结合内存池,批量分配内存 - 自己实现简单的内存池(比如针对
Pet和Property的固定大小内存块分配),减少分配/释放的系统调用开销
- 使用C++17的
三、并发访问的处理
你提到要支持多线程访问relatives,这里需要注意两个核心点:
容器的线程安全
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及以上),允许多个读线程同时访问,只有写线程需要独占锁,适合读多写少的场景;或者用分段锁(把容器分成多个片段,每个片段一个锁)降低锁竞争。
- 最简单的方案:给
对象创建的并发安全
多个线程同时调用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

