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

使用智能指针向消费者传递依赖的现代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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 05:05:06