应选用哪种智能指针?智能指针新手的多线程场景选型疑问
关于智能指针使用的建议:从你的场景出发避坑
嘿,很高兴你一开始就想着避开智能指针的坑,这绝对是个好开头!先给你吃个定心丸:你用std::unique_ptr<A>管理工厂创建的对象,再加入队列交给其他线程处理的思路,完全合理——因为unique_ptr就是为独占所有权、转移所有权的场景设计的,工厂创建的对象所有权从工厂转移到队列,再转移到消费线程,完美匹配它的语义。
接下来针对你提到的「构造A需要C类,C持有自己的B对象列表」的场景,我分几种常见情况给你具体建议,帮你养成正确的习惯:
情况1:A需要完全拥有C的所有权
如果C是专门为这个A创建的,其他地方不会用到C,或者A销毁时C也应该跟着销毁,那A里应该用std::unique_ptr<C>来持有C,延续独占所有权的思路:
// 先定义B、C struct B {}; class C { public: // C自己用unique_ptr管理B列表,这部分你做得没问题 std::vector<std::unique_ptr<B>> b_list; }; // 改造A的定义 class A : public IInterface { public: // 用std::move转移C的所有权给A,避免拷贝(unique_ptr不能拷贝) explicit A(std::unique_ptr<C> c) : c_(std::move(c)) {} private: std::unique_ptr<C> c_; // A独占C的所有权 }; // 工厂创建A的示例代码 auto create_A() { // 优先用make_unique,比直接new更安全(避免构造异常导致内存泄漏) auto c = std::make_unique<C>(); // 给C添加B对象,同样用make_unique c->b_list.push_back(std::make_unique<B>()); // 转移C的所有权给A,再转移A的所有权给队列 auto a = std::make_unique<A>(std::move(c)); queue.push(std::move(a)); return; }
这里要注意两个关键点:
- 所有转移
unique_ptr的地方都要用std::move,因为它不能被拷贝,只能通过移动转移所有权。 - 永远优先用
std::make_unique/std::make_shared创建智能指针,而不是new后手动赋值——前者能避免很多隐蔽的内存泄漏场景(比如构造函数抛出异常时,make系列会自动释放已分配的内存)。
情况2:C是多个对象共享的
如果C是全局实例、或者其他类/线程也需要访问C,那A里应该用std::shared_ptr<C>来持有C,此时C的创建也要用std::shared_ptr:
class C { public: std::vector<std::unique_ptr<B>> b_list; }; class A : public IInterface { public: // 这里可以拷贝shared_ptr(会增加引用计数),或者用std::move(如果不需要原指针了) explicit A(std::shared_ptr<C> c) : c_(std::move(c)) {} private: std::shared_ptr<C> c_; // 和其他持有者共享C的所有权 }; // 工厂创建示例 auto shared_c = std::make_shared<C>(); // 创建多个A,都共享同一个C auto a1 = std::make_unique<A>(shared_c); auto a2 = std::make_unique<A>(shared_c); queue.push(std::move(a1)); queue.push(std::move(a2));
这种场景下,C会在最后一个持有它的shared_ptr销毁时自动释放,不用你手动管理。如果后续出现A和C互相引用的情况(比如C也持有A的指针),记得用std::weak_ptr打破循环引用——不过目前你的场景里暂时不需要考虑这个。
几个通用的好习惯,帮你避开常见坑
- 永远不要手动调用
delete:智能指针的核心价值就是自动管理内存,手动delete会导致双重释放(智能指针还会再释放一次)。 - 多线程访问队列时一定要加锁:
std::queue本身不是线程安全的,生产线程push和消费线程pop时,必须用std::mutex+std::lock_guard(或者std::scoped_lock)保护队列,避免数据竞争。 - 不要把
unique_ptr放到容器里后还保留原指针:一旦你把unique_ptrmove到队列里,原指针就变成空的了,再使用原指针会导致未定义行为。 - 尽量用
explicit构造函数:像上面A的构造函数那样,避免隐式的智能指针转换,减少意外的所有权转移。
总的来说,你一开始的方向完全正确,只要理清各个对象之间的所有权关系,选对对应的智能指针,就能轻松避开大部分坑啦!
内容的提问来源于stack exchange,提问作者Taztingo
相关产品推荐
相关产品推荐

