从没有虚方法的基类继承是一种不好的实践吗?

13

我之前读过一篇关于dynamic_cast的问题的答案。因为基类没有虚函数,所以dynamic_cast失败了。其中一位回答者说从没有虚函数的类派生通常意味着一个糟糕的设计。这正确吗?即使不利用多态性,我仍然看不出这样做的错误之处。

5个回答

9

这要看我们讨论的是什么:

  • 对于特征类(没有数据),这是可以接受的(例如std::unary_function
  • 对于private继承(用于从空基类优化中获益而不是组合),这也是可以接受的

问题出现在你开始针对这个基类多态地处理这样的派生对象时。如果你达到了这样的位置,那么这肯定是一种代码异味

注意:即使如上所述是可以接受的,你仍然提供了使用类多态性的能力,因此会暴露自己于微妙的错误。


多态用法的好处是可以实现 - 不过,如果您的类按照您所概述的那样正确定义,用户有责任不要走额外的路来规避它。 - Ken Wayne VanderLinde
1
@Ken:问题在于这个责任。即使是无意的,用户可能会搞砸并对类进行多态使用。当然,在两种情况下都不太可能发生。在前者中,因为特征没有非静态方法,所以它几乎没用,在后者中,“private”将错误可能性限制在类和友元方法上。但仍然可能发生。问题(总体而言)是缺少一个功能:我们希望在这里进行委托,而不是继承 - Matthieu M.

2

从一个类派生是一种有效的选项,以便重用代码。

有时候我们不需要多态行为。这很好-我们有这个选项的原因。但如果是这种情况,那么考虑使用私有继承-如果你的类不打算成为多态的,那么没有人会试图将其作为多态来使用。


4
仅仅为了代码重用,我不会使用继承。组合更加适合这个任务,而使用继承会让你与基类更紧密地联系在一起,同时也会暴露你于微妙的(转换/隐藏)错误之中。 - Matthieu M.
@Matthieu:一个有说服力的观点,尽管仍然有争议。确实需要注意,以及用户端的责任。我同意组合通常更好,但私有继承仍然是一种选择(尽管使用起来更加棘手)。 - Ken Wayne VanderLinde
3
我发现私有继承在两种情况下可行:作为优化(EBO)或覆盖虚拟方法(此处不涉及)。其他所有情况都是懒惰/方便的问题。我是否提到过我对继承/组合辩论非常紧张? - Matthieu M.
1
私有继承只是组合关系(has-a,而不是is-a关系)。它还具有EBO的额外好处,还可以使用'using'指令将某些基本方法直接公开为类的“public”或“protected”接口。 - Matthias

1

这里有一个不错的例子,将行为因素纳入策略中(请注意受保护的析构函数):

struct some_policy
{
    // Some non-virtual interface here
protected:
    ~some_policy() { ... }

private:
    // Some state here
};

struct some_class : some_policy, some_other_policy { ... };

另一个不错的例子,避免模板中出现的代码膨胀。请注意受保护的析构函数:

struct base_vector
{
    // Put everything which doesn't depend 
    // on a template parameter here

protected:
    ~base_vector() { ... }
};

template <typename T>
struct vector : base_vector
{ ... };

另一个例子是CRTP。请注意受保护的析构函数:

template <typename Base>
struct some_concept 
{
    void do_something { static_cast<Base*>(this)->do_some_other_thing(); }

protected:
    ~some_concept() { ... }
};

struct some_class : some_concept<some_class> { ... };

另一个例子是空基类优化。它并不是真正的继承,因为它更像是一种技巧,允许编译器在some_class中不保留基类(作为私有成员)。
template <typename T>
struct some_state_which_can_be_empty { ... };

template <typename T>
struct some_class : private some_state_which_can_be_empty<T> { ... };

通常情况下,你继承的类应该有虚拟或受保护的析构函数。

我对“public”继承感到担忧。 - Ken Wayne VanderLinde
@Alexandre C:无论是政策还是代码膨胀示例都可以使用组合(只要不需要EBO)。 CRTP示例实际上是特殊的,因为它不允许传统意义上的多态性(基类依赖于派生类)。 - Matthieu M.
@Matthieu:代码膨胀和策略示例通常具有一些非平凡的公共接口。如果将它们作为成员,则必须手动编写包装器。在通用代码和C++03中很繁琐(在C++0x和完美转发中更容易)。 - Alexandre C.
@Matthieu:这不是懒惰。我在能用的时候确实会使用这些工具。 - Alexandre C.

0
在C++中没有虚方法的继承只不过是代码重用而已。我无法想象没有多态性的继承。

0

C++标准库中的一些类具有受保护成员(仅对派生类有意义),但没有虚成员函数。也就是说,它们被设计用于继承,而不具备虚拟性。这证明了从一个没有虚拟成员函数的类派生通常是坏的设计。

祝好!


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