【问题标题】:Virtual base class data members虚拟基类数据成员
【发布时间】:2010-11-14 15:43:32
【问题描述】:

为什么不建议虚拟基类中有数据成员?

函数成员呢? 如果我有一个所有派生类共有的任务,虚拟基类是否可以执行该任务,或者派生类是否应该从两个类继承 - 从虚拟接口和普通基类执行该任务?

谢谢。

【问题讨论】:

  • 您能否提供建议的链接或引用?如果基类没有数据成员,我看不出它有多大意义,因为虚拟继承最常见的目的是防止基类成员的重复。例如,在标准库中,ios_base 具有数据成员,并且是 istream 和 ostream 的虚拟基类(通过 ios)。所以我几乎(但不完全)说相反的 - 如果你要拥有一个虚拟基类,那么它应该有数据成员,或者非虚拟继承。

标签: c++ inheritance virtual


【解决方案1】:

a) 成员应该是私有的 - 因此在派生类中使用它们可能会遇到问题(因此您必须添加 getter 和 setter 方法,这会破坏您的界面)

b) 不要声明你当前不使用的东西 - 只在访问/使用它们的类中声明变量

c) 虚拟基类应该只包含接口/虚拟方法,仅此而已

我希望能有所帮助,即使我的理由并不完美和完整:)

乔, 克里斯

【讨论】:

    【解决方案2】:

    作为一种实践,您应该只使用虚拟继承来定义接口,因为它们通常与多重继承一起使用,以确保派生类中只存在一个版本的类。纯接口是最安全的多重继承形式。当然,如果你知道自己在做什么,你可以随意使用多重继承,但如果你不小心,它可能会导致代码脆弱。

    虚拟继承的最大缺点是它们的构造函数带参数。如果必须将参数传递给虚拟基类的构造函数,则强制所有派生类显式调用构造函数(它们不能依赖调用构造函数的基类)。

    我能看到您明确建议的唯一原因是您的虚拟基类中的数据可能需要构造函数参数。

    编辑 在马丁发表评论后,我做了一些家庭作业,谢谢马林。第一行并不完全正确:

    作为一种练习,您应该只使用 虚拟继承来定义 通常使用的接口 具有多重继承以确保 只有一个版本的类是 存在于派生类中。

    如果基类是纯接口,则虚拟继承没有区别(除了编译器错误略有不同,在 vc8 中,如果所有方法都没有实现)。只有当基类有数据时才会有真正的区别,在这种情况下,你最终会得到一个菱形而不是 U 形

    Non virtual    virtual
      A     A          A
      |     |        /   \
      B     C       B     C
       \   /         \   /
         D             D
    

    在虚拟情况下,B 和 C 共享 A 的同一个副本。

    然而,我仍然同意纯接口是最安全的多重继承形式,即使它们不需要虚拟继承。并且构造函数参数和虚拟继承是一个痛苦的事实。

    【讨论】:

    【解决方案3】:

    我从未见过此建议。

    类是一组密切相关的功能和数据。基类的目的是拥有一组可供派生类重用的通用函数和数据。

    我认为限制自己在基类中不包含数据成员或非纯虚函数会减少代码重用量,从长远来看会导致代码可靠性降低。

    【讨论】:

    • 如果你能得到一份“Effective C++”的副本,请阅读第 20 条:“避免公共接口中的数据成员”。
    • 好吧,我同意这一点。我并不是建议数据成员是公开的。无论如何,我误解了这个问题,并认为iain的答案很好。
    【解决方案4】:

    核心建议是在虚拟基础中拥有一个默认构造函数。如果你不这样做,那么每个派生最多的类(即任何子类)都必须显式调用虚拟基 ctor,这会导致愤怒的同事敲你办公室的门......

    class VirtualBase {
    public:
        explicit VirtualBase( int i ) : m_i( i ) {}
        virtual ~VirtualBase() {}
    
    private:
        int m_i;
    };
    
    class Derived : public virtual VirtualBase {
    public:
        Derived() : VirtualBase( 0 ) {} // ok, this is to be expected
    };
    
    class DerivedDerived : public Derived { // no VirtualBase visible
    public:
        DerivedDerived() : Derived() {} // ok? no: error: need to explicitly
                                        // call VirtualBase::VirtualBase!!
        DerivedDerived() : VirtualBase( 0 ), Derived() {} // ok
    };
    

    【讨论】:

    • 你的意思是如果我没有显式调用虚拟基础 ctor 将不会在我创建派生对象时被调用?
    • 必须每个派生的ctor的实现中调用虚拟基类ctor,否则编译器不会让你侥幸逃脱。唯一的例外:如果虚拟基础有一个默认的 ctor,那么当您没有显式调用另一个时,就会调用它。
    【解决方案5】:

    用于在 C++ 中模拟接口的全抽象基类不应该有数据成员 - 因为它们描述的是一个接口,而实例状态是一个实现细节。

    除此之外,包含虚函数的基类很可能有数据成员。但是,通常的规则适用:

    • 尽可能隐藏它们,如果需要保留类不变量,请使用 getter 和 setter。
      (这里有一个漏洞:如果没有与成员关联的不变量,即它可以在任何给定时间假定任何可能的值,您可以将其公开。但是,这会拒绝派生类添加不变量。
    • 继承设计:类的契约应定义派生类的职责和可能性,它需要/可以覆盖什么目的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-04
      • 2023-03-23
      • 2015-12-15
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多