如果您总是将接口存储在shared_ptr中,那么需要一个虚析构函数吗?

13

由于boost::/std::shared_ptr具有类型擦除其删除器的优点,因此您可以做出很棒的事情,例如:

#include <memory>

typedef std::shared_ptr<void> gc_ptr;

int main(){
  gc_ptr p1 = new int(42);
  gc_ptr p2 = new float(3.14159);
  gc_ptr p3 = new char('o');
}

通过正确保存正确的删除器,这将正确地删除所有指针。

如果您确保接口的每个实现始终使用shared_ptr<Interface>(或make_shared<Interface>)创建,那么您真的需要一个virtual析构函数吗?无论如何我会声明它为virtual,但我只是想知道,因为shared_ptr将始终删除其初始化的类型(除非提供另一个自定义删除器)。


1
可能是shared_ptr magic :)的重复问题。 - Armen Tsirunyan
1
@David:不,他没有。他说他仍然会使用虚析构函数。他正在问是否可以没有虚析构函数。所以这是一个重复的问题。 - Armen Tsirunyan
2
是的,这是真的。然而,我个人会担心这样做。有一天我可能会决定“哦,这不需要一个shared_ptr,我只需使用指向基类的指针”,然后一切都会悄悄地崩溃。我认为这是脆弱的代码,外部代码对类的实现方式做出合理的假设可能会轻易地破坏它,并且除非我能证明必须这样做,否则不会这样做。 - jcoder
你的示例是否实际上对shared_ptr的类型擦除进行了非平凡的使用?在所有三种情况下,删除器都只是delete(void*)。我没有看到自定义删除器和通过基指针进行删除之间的联系。 - Kerrek SB
1
@Kerrek:不,这三种情况的删除器是不同的。它们都可能采用void*,但分别将其转换为正确的类型,即intfloatchar - Xeo
显示剩余2条评论
1个回答

14

对于那些被设计用来衍生的类,我仍然会遵循常规规则:

提供一个公有虚析构函数或者受保护的非虚析构函数

原因是您无法控制所有的使用情况,而这个简单的规则意味着如果您试图通过错误级别在层次结构中删除代码,则编译器将标志。请考虑,shared_ptr不能保证它将调用适当的析构函数,只保证它将调用用作参数的静态类型的析构函数:

base* foo();
shared_ptr<base> p( foo() );
如果base有一个公共的非虚拟析构函数并且foo返回从base派生的类型,则shared_ptr将无法调用正确的析构函数。如果base的析构函数是虚拟的,则一切都会没问题,如果是受保护的,则编译器会告诉你那里有一个错误。

我无论如何都会将其声明为虚拟的[...]。你提到不能控制所有实例化点是一个好观点。不过,你总是可以通过使用命名构造函数来解决问题,但那可能看起来不太好。 - Xeo
1
警告:受保护的析构函数当前不会对 is_nothrow_destructible<T>::value 返回 true,即使它们不会抛出异常。因此,我更倾向于使用公共选项。 - Howard Hinnant
@Howard:感谢您提供关于is_nothrow_destructible的信息。它似乎做了正确的事情。为什么您会因为它当前正确地报告了一个不可销毁的东西而将该东西更改为可销毁呢? - Cheers and hth. - Alf
1
@Xeo: 你真的可以控制实例化吗?如果你的类是作为一个基类存在的,那么这意味着我可以编写自己的扩展,并且你无法控制我如何允许用户实例化我的对象。 - David Rodríguez - dribeas
最终,其他程序员总是可以按照他们的意愿去做。你真正能做的最好的事情就是制定一份详细的政策文件,并设置路障以防止规避尝试。 - Deduplicator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接