使用智能指针向消费者传递依赖的现代C++最佳实践
现代C++智能指针依赖传递:最佳实践与替代方案
你的实现是合理且符合现代C++设计理念的,先直接给结论:用引用传递依赖来隐藏智能指针的做法,是完全正确的选择。
为什么你的引用方案可行?
优点:
- 接口清晰解耦:
WidgetConsumer只依赖Widget抽象,不需要关心外部是用智能指针、栈对象还是其他方式管理Widget的生命周期。这完全符合依赖倒置原则,让消费者的复用性更强——比如未来把Widget改成栈分配,消费者代码不需要任何改动。 - 状态同步天然实现:引用是对象的别名,原
Widget的状态变化会直接反映在消费者持有的引用上,完全满足你“状态同步”的需求。 - 所有权边界明确:智能指针(比如你的
unique_ptr)在外部全权负责Widget的生命周期,消费者不参与销毁逻辑,避免了所有权混乱的问题。
需要注意的风险:
引用方案的核心风险是悬空引用:如果Widget的生命周期短于WidgetConsumer,当Widget被销毁后,消费者再访问_widget就会触发未定义行为。你需要确保WidgetConsumer的销毁顺序始终早于它引用的Widget(比如你的示例中widgetConsumerB先销毁,widgetB后销毁,是安全的)。
可参考的替代方案
1. 共享所有权:std::shared_ptr传递
如果无法保证Widget的生命周期始终长于消费者,或者需要消费者和外部共享Widget的所有权,可以让消费者持有std::shared_ptr<Widget>。
修改后的WidgetConsumer示例:
// 头文件 #include <memory> class WidgetConsumer { public: std::shared_ptr<Widget> _widget; explicit WidgetConsumer(std::shared_ptr<Widget> widget) : _widget(std::move(widget)) {} virtual ~WidgetConsumer(); };
- 优点:自动管理生命周期,彻底避免悬空引用;
- 缺点:接口暴露了智能指针,消费者需要知晓所有权共享的逻辑。
2. 弱引用:std::weak_ptr配合shared_ptr
如果消费者不需要共享所有权,但又想避免悬空引用,可以让外部用shared_ptr管理Widget,消费者持有std::weak_ptr<Widget>。每次访问前需要lock()检查对象是否存活:
// 头文件 #include <memory> class WidgetConsumer { public: std::weak_ptr<Widget> _widget; explicit WidgetConsumer(std::weak_ptr<Widget> widget) : _widget(std::move(widget)) {} void doSomething() { if (auto ptr = _widget.lock()) { // 安全访问ptr->cost } else { // Widget已经被销毁,处理逻辑 } } virtual ~WidgetConsumer(); };
- 优点:不影响
Widget的生命周期,避免悬空引用; - 缺点:接口暴露智能指针,每次访问需要额外的检查逻辑。
3. 观察者模式(Observer Pattern)
如果需要更主动的状态同步(比如Widget状态变化时,消费者能立即收到通知),可以使用观察者模式:
// Widget作为主题 class Widget { public: int cost { 0 }; virtual ~Widget(); void setCost(int newCost) { cost = newCost; notifyObservers(); } void addObserver(class WidgetObserver* observer); void removeObserver(class WidgetObserver* observer); private: std::vector<WidgetObserver*> _observers; void notifyObservers(); }; // 观察者基类 class WidgetObserver { public: virtual ~WidgetObserver() = default; virtual void onWidgetCostChanged(int newCost) = 0; }; // WidgetConsumer实现观察者接口 class WidgetConsumer : public WidgetObserver { public: void onWidgetCostChanged(int newCost) override { // 处理状态变化,比如更新本地缓存或执行逻辑 std::cout << "Consumer received cost update: " << newCost << std::endl; } };
- 优点:彻底解耦,消费者不需要持有
Widget的引用,只在状态变化时响应; - 缺点:增加了代码复杂度,适合状态变化频繁且需要主动响应的场景。
你的顾虑是否正确?
你“不想让消费者知晓智能指针存在”的想法完全正确——接口应该聚焦于业务依赖,而非具体的内存管理细节。隐藏智能指针能让消费者的职责更单一,也让代码更易维护和复用。
附:你提供的示例代码
头文件
class Widget { public: int cost { 0 }; virtual ~Widget(); }; class WidgetConsumer { public: Widget& _widget; explicit WidgetConsumer(Widget& widget); virtual ~WidgetConsumer(); };
源文件
#include <iostream> #include "widgets.h" int main() { std::unique_ptr<Widget> widgetB = std::make_unique<Widget>(); WidgetConsumer widgetConsumerB(*widgetB); widgetB->cost = 23; std::cout << "widget.cost=" << widgetB->cost << std::endl; std::cout << "widgetConsumer._widget.cost=" << widgetConsumerB._widget.cost << std::endl; } WidgetConsumer::WidgetConsumer(Widget &widget) : _widget(widget) { } WidgetConsumer::~Widget() { std::cout << "WidgetConsumer destroyed" << std::endl; } Widget::~Widget() { std::cout << "Widget destroyed" << std::endl; }
输出结果
widget.cost=23 widgetConsumer._widget.cost=23 WidgetConsumer destroyed Widget destroyed
内容的提问来源于stack exchange,提问作者Scruffers
相关产品推荐
相关产品推荐

