【问题标题】:Inheriting from classes without virtual destructors从没有虚拟析构函数的类继承
【发布时间】:2012-03-28 14:18:07
【问题描述】:

我一直听说你不应该从没有虚拟析构函数的类继承,而我并没有太在意,因为我只是不经常使用继承。即使您不想使用多态性,但您只想要一个类的所有功能,并且想要添加更多功能,这条规则是否也适用?具体来说,只要我不以多态方式使用它,下面的类是否是安全的、具有明确定义的行为? (即不删除派生对象的基指针)

template<typename T>
class SomewhatSafeVector : public std::vector<T>
{
public:
    typedef std::vector<T> base;

    T& operator[](unsigned n) {
        if (n >= base::size())
        {
            throw IndexOutOfBounds();
        }
        return base::operator[](n);
    }
};

【问题讨论】:

  • 如果不用多态就可以了。
  • 不管它是否可以,但您仍然不应该从标准库容器派生。此外,如果您在访问动态容器的范围内遇到问题,您可能更愿意查看您的全局算法思维(想想“0-1-many”和“范围”),因为越界访问是通常是一个逻辑错误。
  • 我认为在您的特定示例中,继承并不是一个非常优雅的解决方案,因为继承是为了接口重用,而不是实现重用。您显然不重用该接口,因为您的 operator[] 会引发 std::vector 不会的异常。如果您想重用代码,只需使用普通的共享函数或(如本例所示)将std::vector 设为SomewhatSafeVector 的成员。
  • @KerrekSB:第一个,为什么不呢?到第二个,我没有这样的麻烦。但我认为边界检查容器对于教学和调试来说是一个好主意。
  • @FrerichRaabe:我大体上同意。而且我实际上已经完全实现了一个边界检查向量类,它带有边界检查迭代器,它就是这样做的。我只是想知道是否可以通过使用继承来节省一些时间,以免重写不需要边界检查的函数。

标签: c++ inheritance


【解决方案1】:

我一直听说你不应该从没有虚拟析构函数的类继承

这是给初学者的经验法则,因为解释所有错综复杂的东西需要花费太多时间,而且实际上只给他们一些始终有效的基线会更安全(而且对于练习程序来说成本也不高) (虽然可能有点矫枉过正)。

您可以完美地使用继承,而无需在基类中使用virtual 析构函数。另一方面,如果基类根本没有virtual 方法,那么继承可能是不适合这项工作的工具。 例如:如果我使用SafeVector&lt;T&gt; sv; sv[3];,那么它是安全的,但是如果我使用std::vector&lt;T&gt;&amp; v = sv; v[3];,则不是......这是因为你只是隐藏 基类方法,而不是覆盖它(提高您的警告级别,他们会通知您)。

这里的正确方法是使用组合,然后为您真正使用的方法创建转发方法到实现成员。在实践中,它会变得很累,因为 C++ 不支持委托 (using attribute.insert;),所以很多人求助于继承...

另一种选择是提供更安全的方法作为免费方法,因为您始终可以不受限制地添加免费方法。有“OO”心态的人可能会觉得不那么地道,有些运算符不能这么加。

【讨论】:

    【解决方案2】:

    如果您不打算以多态方式使用该类(不删除指向派生对象的基指针),则它不是未定义行为。

    参考:

    C++03 标准:5.3.5 删除

    5.3.5/1:

    delete-expression 操作符销毁由 new-expression 创建的最派生对象 (1.8) 或数组。
    删除表达式:
    ::opt 删除强制转换表达式
    ::opt delete [ ] 强制转换表达式

    5.3.5/3:

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

    【讨论】:

      【解决方案3】:

      欢迎您以多态方式使用该对象,但您不能以多态方式delete 它。如果您避免通过std::vector&lt;&gt;* 删除指向您的类对象的指针,那么您是安全的。

      除此之外:您可以这样简化您的operator[]

      T& operator[](unsigned n) { return this->at(n); }
      

      【讨论】:

        【解决方案4】:

        是的,如果您从不使用多态性(即从不向上转换引用或指针),则无法执行不安全的销毁。

        Mixin 类经常以这种方式使用,CRTP 很少暗示虚拟析构函数,仅举几个模式。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-08-14
          • 2020-03-10
          • 2011-01-12
          • 2015-06-21
          • 1970-01-01
          • 2013-01-27
          • 2012-09-09
          • 2013-10-24
          相关资源
          最近更新 更多