C++ 抽象工厂是否应该为构造的对象提供 destroy 方法?

Should C++ abstract factory provide destroy method for constructed objects?

考虑下面的接口(使用哑指针是因为我们还在 C++98)

class WidgetMaker {
    virtual Widget* makeWidget() = 0;
};

可能采用以下实施方式

class SpecificWidgetMaker: public WidgetMaker {
    Widget* makeWidget() {
        return new SpecificWidget();
    }
};

Widget 是一些带有虚拟析构函数的基础 class,SpecificWidget 扩展了它。 我的同事声称 WidgetMaker 接口应该包含以下方法

virtual void freeWidget(Widget* widget);

基本原理是这样我们不强制 makeWidget 实现使用标准的新分配,它们可以使用自定义池分配器或总是 return 相同的全局实例以防小部件是无状态的或其他。

我觉得这样的设计通常不是一个好主意——它使客户端代码复杂化,违反了 KISS 和 YAGNI,使得向 unique_ptr 的过渡(在未来 20 年内在我们的组织中不太可能)变得更加困难。我应该相信自己的感觉吗?在哪些情况下,自由方法作为抽象工厂接口的一部分是合理的?

您朋友提出的解决方案(实际上,您的原始解决方案也是如此)的一个问题是它有一个不必要的重要协议。通过 makeWidget 获得 Widget 后,您需要记住释放它(直接释放,或通过调用工厂的某些方法)。众所周知,这是脆弱的——它要么会很快崩溃(导致 Widget 泄漏),要么会使客户端代码真正复杂化。

如果你看一下std::shared_ptr::shared_ptr(...)的接口,你会发现它可以带一个自定义删除器对象

因此,也许你可以 typedef(或等价物)Widget 智能指针到底是什么:

using WidgetPtr = std::shared_ptr<Widget, ...>

如果您稍后决定在 Widget 被释放时工厂需要执行某些操作,您可以将 typedef 更改为使用自定义删除器的那个,这个自定义删除器可以通知工厂一个对象正在被删除。

这样做的主要优点是它消除了记住释放用户的 Widget 的责任。

我想说这里没有可遵循的一般经验法则。这完全取决于您 Widget 实施的细节。

如果正确销毁任何 Widget 子类的实例所需的一切都可以在其普通析构函数中处理,则在您的因子中显式声明 freeWidget() 没有任何用处.它不会增加任何价值。

另一方面,如果出于某种原因需要在析构函数中执行某些无法处理的操作,那么很明显,您需要一个明确的方法来销毁您的小部件。

也许目前不需要任何此类特殊处理,但您预见到将来需要这样做。在这种情况下,声明一个显式的 freeWidget() 方法是有意义的,以避免以后重写一堆代码。

如果您决定采用 freeWidget() 方法,您可能会考虑做的一件事是将所有子类的析构函数设为私有(最有可能使用一些合适的 friend 声明,因此 有些东西实际上可以摧毁这些东西),以执行此政策。

为什么您可能想要显式 freeWidget() 的一个例子是例外。从析构函数中抛出异常是......敏感的。这是允许的,但它带有某些...限制。因此,如果销毁您的小部件可能会抛出异常,使用 freeWidget() 将使您能够更好地控制抛出异常,在这种情况下,并在这样做之前正确清理被销毁的小部件.