【问题标题】:Virtual destructor: is it required in base class if base class allocated memory dynamically?虚拟析构函数:如果基类动态分配内存,基类是否需要?
【发布时间】:2012-09-01 10:58:16
【问题描述】:

这个问题看起来像Virtual destructor: is it required when not dynamically allocated memory?中的讨论

在一个考试问题中,有人问我: - 维护指向动态分配内存的指针的基类应该定义什么?

我回答: - 一个复制构造函数和一个赋值运算符(以确保不仅复制指针......参见深拷贝)和一个析构函数(释放分配的内存)

他们说这是不正确的,因为这个基类还应该定义一个虚拟析构函数而不是普通的析构函数。为什么?

【问题讨论】:

  • 你应该问你的考官,而不是我们。她是断言你需要一个虚拟析构函数的人。
  • 您完全有权要求考官解释原因,这应该是您的第一步。
  • 关于包含动态分配的资源的内容有点像红鲱鱼。关键是“基类”,这意味着继承,因此可以通过基类类型指针删除派生类,这是您需要担心虚拟析构函数的地方。

标签: c++ virtual-destructor


【解决方案1】:

如果您的类打算以多态方式使用,您可能会有指向派生对象的基类的指针。

通过指向没有virtual 析构函数的基类的指针删除派生对象会导致未定义的行为。这可能是她的理由。

5.3.5

3) 在第一种选择(删除对象)中,如果静态类型为 操作数与其动态类型不同,静态类型应 是操作数的动态类型和静态类型的基类 应具有虚拟析构函数或行为未定义。 [...]

【讨论】:

    【解决方案2】:

    您的基类需要一个虚拟析构函数如果派生类的对象打算通过基类指针销毁,就像这样

    Base *pointer_to_base_class = new Derived;
    delete pointer_to_base_class;
    

    根据您的问题,尚不清楚是否是这种情况。也许问题的另一部分(或先前的问题)清楚地表明这种多态破坏是有意的。或者,也许您在课堂上被教导要始终将这种使用视为最佳实践。

    【讨论】:

    • +1 还澄清了该特定问题中没有任何内容暗示需要虚拟销毁。
    • 如果您使用类层次结构,那么这种多态性可能会发生,因此在设计上使用虚拟析构函数_应该_完成。至少,在任何非平凡的应用程序中。
    【解决方案3】:

    它们不是 100% 正确的。虚拟析构函数是必须的,如果

    1. 与动态多态和使用的类层次结构
    2. 派生对象通过指向基的指针销毁。

    否则非虚析构函数是可以的。但在大多数情况下,即使只打算使用 #1,将析构函数设为虚拟也是一种很好的风格,而不管 #2 是什么。

    【讨论】:

      【解决方案4】:

      添加到其他答案:您还可以设想一种情况,您确实需要一个公共基类,但您没有任何实际的接口函数。但是如果你想要 RTTI 和动态转换支持,你需要在你的类中添加一个虚函数。析构函数可以就是那个函数。

      例如,假设您是一名正在康复的 Java 程序员,并坚持认为一切都是Object。你可以像这样开始你的第一个 C++ 程序:

      class Object
      {
      public:
         virtual ~Object() { }
      };
      

      现在Object 确实可以作为每个类的最终多态基类。

      如果你也认为Object应该是抽象的,你甚至可以将析构函数设为纯虚拟:

      class Object { public: virtual ~Object() = 0; }; Object::~Object() { }
      

      【讨论】:

      • 当然,你实际上不应该这样做,所以这是一个愚蠢的理由。
      • 将我的评论视为解释性注释 :) 我认为您比我更了解这一点。
      • 当你不确定时,你声明它protected。所以如果你真的需要它,MS Visual Studio Compiler 2010 会告诉你。
      【解决方案5】:

      为了跟进所有好的答案,当一个类应该被子类化以形成层次结构并且你想要通过指向它的指针删除派生对象。 C++ 标准对此很清楚:

      当你想通过基类删除派生类对象时 指针和基类的析构函数不是虚拟的 结果未定义

      通过未定义的行为,您可能会想到内存泄漏,例如,如果您的派生类分配了一些动态内存并且您稍后尝试通过此基类删除它。您的老师可能正在考虑这种情况。

      【讨论】:

      • 2:我不知道你在说什么 1:这是一个道理,我不必回答任何老师 0:我看我没有提供参考,但我寻找这个答案 10 年。 .
      • 这不是关于“适当的清理”——这意味着如果用户有可以通过基指针deleted 的派生类,就懒得写一个虚拟析构函数,并且没有或关心内存泄漏 - 这样的delete 在某种程度上是可以的,因为它们没有什么可“清理”的,或者不会被小泄漏所困扰。 不是好的:它是纯 UB - 所以如果出现 delete,他们的整个程序就会变成定时炸弹;不管它最终是否泄漏,它都不能再被信任了。必须不惜一切代价避免 UB。
      【解决方案6】:

      在标准中,大多数继承层次结构在其基础上都有一个虚拟析构函数;然而,sub_match 被定义为从std::pair<BidirectionalIterator, BidirectionalIterator> 公开继承,因此它可以拥有动态分配的内存。在相关领域,match_results 不是必需的,但通常实现公共继承自 std::vector<...>,这肯定会分配内存。

      您的考官并非完全不正确,但对动态分配内存的关注是一种转移注意力的做法,并且暴露了令人担忧的对标准的无知;虽然在大多数实现中,通过指向没有虚拟析构函数的基类型的指针删除派生类型将导致破坏切片对象,但根据标准,这是未定义的行为。

      【讨论】:

      • 是的,也许使用新的 c++ rtti 用法(dynamic_cast,typeid),我们可以用模板和“中间代码生成”做更多的事情......在这种情况下,你是对的......应该总是将析构函数定义为虚拟的,因为您不知道添加的代码将如何/如何使用您的班级。也许我会研究如何通过使用新的关键字(non copyables、final、delete 等)来采用防御性编程
      猜你喜欢
      • 2021-08-22
      • 2011-02-03
      • 1970-01-01
      • 2012-06-26
      • 1970-01-01
      • 2019-04-20
      • 2014-10-02
      • 2014-03-23
      • 2019-10-11
      相关资源
      最近更新 更多