2012-03-28 52 views
7

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

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); 
    } 
}; 
+1

不要介意它是否正常,但你仍然不应该从标准库容器派生。此外,如果您在访问动态容器时遇到困难,您可能更愿意考虑您的全局算法思维(想一想“0-1-many”和“ranges”),因为out-bound-bound访问是通常是一个*逻辑*错误。 – 2012-03-28 14:31:13

+4

我认为在你的具体例子中,继承并不是一个非常优雅的解决方案,因为继承意味着接口重用,而不是实现重用。你显然不会重用接口,因为你的'operator []'抛出了一个'std :: vector'不会的异常。如果要重用代码,只需使用普通共享函数或(如本例中那样),使'std :: vector'成为'SomewhatSafeVector'的成员。 – 2012-03-28 14:31:40

+0

@KerrekSB:首先,为什么不呢?第二,我没有这样的麻烦。但是我认为边界检查容器对于教学和调试目的来说是个好主意。 – 2012-03-28 14:34:22

回答

6

我一直听说,你不应该不虚析构函数

这是给初学者,因为解释所有复杂花费太多时间,这是一个经验法则从类继承只是更安全(对于实践项目来说并不那么昂贵),实际上只给他们几条工作时间很长的基线(尽管可能是矫枉过正)。

在基类中,如果不使用virtual析构函数,则可以完美地使用继承。另一方面,如果基类完全没有virtual方法,那么继承可能是工作的错误工具。 例如:你的情况,如果我用SafeVector<T> sv; sv[3];那么它是安全的,但如果我这样做std::vector<T>& v = sv; v[3];它不是...这是因为你只是躲在基类的方法,而不是覆盖它(杀青你的警告级别,他们会让你知道)。

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

另一种方法是提供更安全的方法作为自由方法,因为您可以随时添加免费方法而不受限制。对于那些拥有“面向对象”思维的人来说,这可能不那么习惯,而且有些操作员不能这么加。

5

如果你不打算使用多态类(不删除基础指针派生的对象),那么它是不是未定义行为。

参考:

C++ 03标准:5.3.5删除

5.3.5/1:

该删除表达式运算符销毁大多数派生对象(1.8)或由新表达式创建的数组。
删除表达式:
::选择删除铸表达
::选择删除[]铸表达

5.3.5/3:

在第一如果操作数的静态类型与其动态类型不同,静态类型应该是操作数动态类型的基类,并且静态类型应该具有虚拟析构函数或行为未定义。在第二种方式(删除阵列)如果动态类型的对象从它的静态类型删除不同,该行为是undefined.73)

4

欢迎您多态的使用对象,你才能它是多态的。如果您避免通过std::vector<>*删除指向您班级对象的指针,那么您是安全的。

除了:您可能简化您operator[]这样的:

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

是的,如果你从来没有使用多态(即从来没有上溯造型引用或指针),没有办法进行不安全的破坏。

Mixin类通常以这种方式使用,并且CRTP很少隐含虚拟析构函数,以命名一些模式。