从没有虚拟方法的基类继承是不好的做法吗?

Is inheritance from a base class with no virtual methods bad practice?

提问人:Luchian Grigore 提问时间:8/9/2011 更新时间:8/9/2011 访问量:7590

问:

我读过一个关于dynamic_cast的问题的答案。dynamic_cast无法工作,因为基类没有虚拟方法。其中一个答案是,从没有虚拟方法的类派生通常意味着糟糕的设计。这是正确的吗?即使没有利用多态性,我仍然看不出这样做有什么错误。

C++ 继承

评论


答:

9赞 Matthieu M. 8/9/2011 #1

这取决于我们在说什么:

  • 对于 Traits 类(无数据),这很好(我想到)std::unary_function
  • 对于继承(用于代替组合以从空基础优化中受益)也很好private

当您开始以多态方式处理此类 Derived 对象时,问题就来了。如果你曾经达到这样的位置,那么它肯定是代码味道

注意:即使上面指出的很好,您仍然提供了以多态方式使用该类的能力,因此您将自己暴露在微妙的错误中。

评论

0赞 Ken Wayne VanderLinde 8/9/2011
关于多态用法的好点是可能的 - 但是,如果你的类按照你概述的那样被正确定义,用户有责任不付出额外的努力来规避这一点。
1赞 Matthieu M. 8/10/2011
@Ken:问题出在这份责任上。即使无意中,用户也可能会搞砸并以多态方式使用该类。当然,在这两种情况下都不太可能。在前者中,因为特征没有非静态方法,所以它是无用的,而在后者中,错误的可能性限制在类和朋友的方法上。不过,它可能会发生。问题(总的来说)是一个缺失的功能:我们在这里想要委派,而不是继承private
0赞 n0rmzzz 8/9/2011 #2

在 C++ 中没有虚拟方法的继承只不过是代码重用。 我想不出没有多态性的继承。

2赞 Ken Wayne VanderLinde 8/9/2011 #3

为了代码重用,从类派生始终是一个有效的选项。

有时,我们不是在寻找多态性行为。没关系 - 我们有这个选择是有原因的。但是,如果是这种情况,那么请考虑改用私有继承 - 如果你的类不是多态的,那么任何人都没有理由尝试多态使用它。

评论

4赞 Matthieu M. 8/9/2011
我不会仅仅将继承用于代码重用。组合我们更适合任务,并且使用 Inheritance 将您与基类更紧密地联系在一起,并使您暴露于微妙的(转换/隐藏)错误。
0赞 Ken Wayne VanderLinde 8/9/2011
@Matthieu:一个有效的论点,尽管仍有争议。的确,它肯定需要用户的关注和责任。我同意组合通常更好,但私人继承仍然是一种选择(尽管正确使用更棘手)。
3赞 Matthieu M. 8/10/2011
我发现私有继承在两种情况下是可行的:作为优化(EBO)或覆盖虚拟方法(这里不关心)。所有其他的都是懒惰/方便的问题。我有没有提到我对继承/作曲的辩论非常紧张?
1赞 Matthias 5/6/2020
私有继承只是组合(has-a 而不是 is-a 关系)。它具有 EBO 的额外好处,但也可以使用指令直接将某些基方法公开为类的 or 接口的一部分。usingpublicprotected
1赞 Alexandre C. 8/9/2011 #4

下面是一个 OK 示例,用于将行为因素分解到策略中(请注意受保护的析构函数):

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

private:
    // Some state here
};

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

另一个 Ok 示例,以避免模板中的代码膨胀。请注意受保护的析构函数:

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> { ... };

根据经验,您继承的类应该具有虚拟析构函数或受保护的析构函数。

评论

0赞 Ken Wayne VanderLinde 8/9/2011
我发现继承问题令人担忧。public
0赞 Matthieu M. 8/9/2011
@Alexandre C:策略和代码膨胀示例都可以改用组合(只要不需要 EBO)。CRTP 示例实际上很特殊,因为它不允许传统意义上的多态性(基类取决于派生类)。
0赞 Alexandre C. 8/9/2011
@Matthieu:代码膨胀和策略示例通常有一些不平凡的公共接口。如果你让它们成为成员,那么你必须手写包装器。通用代码和 C++03 繁琐(使用 C++0x 和完美转发更容易)。
0赞 Alexandre C. 8/10/2011
@Matthieu:这不是懒惰。如果可以的话,我确实会使用这些东西。
0赞 Cheers and hth. - Alf 8/9/2011 #5

C++ 标准库中的某些类具有受保护的成员(仅对派生类有意义),但没有虚拟成员函数。也就是说,它们是为派生而设计的,没有虚拟。这证明,从没有虚拟的类派生出来通常一定是糟糕的设计。

干杯 & hth.,