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
() 将使您能够更好地控制抛出异常,在这种情况下,并在这样做之前正确清理被销毁的小部件.
考虑下面的接口(使用哑指针是因为我们还在 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
() 将使您能够更好地控制抛出异常,在这种情况下,并在这样做之前正确清理被销毁的小部件.