代码之家  ›  专栏  ›  技术社区  ›  Oak

C++专用虚拟继承问题

  •  20
  • Oak  · 技术社区  · 15 年前

    在下面的代码中,类C似乎没有访问的构造函数,这是由于虚拟继承而必需的。然而,代码仍在编译和运行。为什么有效?

    class A {};
    class B: private virtual A {};
    class C: public B {};
    
    int main() {
        C c;
        return 0;
    }
    

    此外,如果我从中移除默认的构造函数,例如

    class A {
    public:
        A(int) {}
    };
    class B: private virtual A {
    public:
        B() : A(3) {}
    };
    

    然后

    class C: public B {};
    

    会(意外)编译,但是

    class C: public B {
    public:
        C() {}
    };
    

    无法按预期编译。

    使用“G++(GCC)3.4.4(Cygming Special,GDC 0.12,使用DMD 0.125)”编译的代码,但已验证其他编译器的行为也相同。

    3 回复  |  直到 13 年前
        1
  •  14
  •   Kirill V. Lyadvinsky    15 年前

    根据 C++ Core Issue #7 无法从派生具有虚拟专用基的类。这是编译器中的错误。

        2
  •  6
  •   Johannes Schaub - litb    15 年前

    对于第二个问题,这可能是因为你没有让它含蓄地 定义 . 如果构造函数只是隐式声明的,则没有错误。例子:

    struct A { A(int); };
    struct B : A { };
    // goes fine up to here
    
    // not anymore: default constructor now is implicitly defined 
    // (because it's used)
    B b;
    

    对于第一个问题,这取决于编译器使用的名称。我不知道该标准指定了什么,但是这个代码是正确的,因为外部类名(而不是继承的类名)是可访问的:

    class A {};
    class B: private virtual A {};
    class C: public B { C(): ::A() { } }; // don't use B::A
    

    也许这个标准在这一点上还没有明确规定。我们得看看。


    代码似乎没有任何问题。此外,有迹象表明代码是有效的。(虚拟)基类子对象是默认初始化的-没有任何文本表明类名的名称查找在 C . 标准规定如下:

    12.6.2/8 (c++0x)

    如果给定的非静态数据成员或基类不是由mem初始值设定项ID(包括大小写)命名的 其中没有mem初始值设定项列表,因为构造函数没有ctor初始值设定项),并且实体不是抽象类的虚拟基类

    […]否则,将默认初始化实体。

    C++ 03有类似的文本(你不太清楚的文本-它只是说它的默认构造函数在一个地方被调用,而在另一个地方,它使它依赖于该类是否是一个POD)。为了让编译器默认地初始化子对象,它只需要调用它的默认构造函数-不需要首先查找基类的名称(它 已经知道 考虑什么基础)。

    考虑一下这个代码,它当然是有效的,但是如果这个 完成(参见 12.6.2/4 在C++ 0x中)

    struct A { };
    struct B : virtual A { };
    struct C : B, A { };
    C c;
    

    如果编译器的默认构造函数只是查找类名 A 里面 C 对于要初始化的子对象,它将有一个不明确的查找结果,因为 和虚拟 找到的类名。如果您的代码的格式不正确,我想说,这个标准肯定需要澄清。


    对于构造函数,请注意 12.4/6 关于的析构函数 C :

    调用所有析构函数时,就像使用限定名引用它们一样,也就是说,忽略更多派生类中任何可能的虚拟重写析构函数。

    这可以用两种方式解释:

    • 调用a::~()
    • 呼叫::A::~A()。

    在我看来,这里的标准不太明确。第二种方法将使其有效(通过 3.4.3/6 c++ 0x,因为这两个类名 在全局范围内查找),而第一个将使其无效(因为两者都是 将找到继承的类名)。它还取决于搜索以什么子对象开始(我相信我们必须使用虚拟基类的子对象作为起点)。如果这样的话

    virtual_base -> A::~A();
    

    然后,我们将直接找到虚拟基“类名作为公共名,因为我们不需要遍历派生类”的作用域,并将该名称作为不可访问的名称。同样,其推理是相似的。考虑:

    struct A { };
    struct B : A { };
    struct C : B, A {
    } c;
    

    如果析构函数只调用 this->A::~A() ,此调用将无效,因为的查找结果不明确 作为继承的类名(不能从作用域引用直接基类对象的任何非静态成员函数 C 10.1/3 ,C++ 03)。它必须唯一地标识所涉及的类名,并且必须以类的子对象引用开始,例如 a_subobject->::A::~A(); .

        3
  •  2
  •   AProgrammer    15 年前

    虚拟基类总是从最派生的类(这里是C)初始化的。编译器必须检查构造函数是否可访问(即,对于

    class A { public: A(int) {} };
    class B: private virtual A {public: B() : A(0) {} };
    class C: public B {};
    
    int main() {
        C c;
        return 0;
    }
    

    虽然您的描述暗示没有),但事实上,作为基础,a是私有的或不重要(颠覆将很容易: class C: public B, private virtual A )。

    从最派生的类调用虚拟基类构造函数的原因是,在任何将其作为基类的类之前,都需要先构造它。

    编辑:基里尔提到了一个旧的核心问题,这与我的阅读和最近编译器的行为很奇怪。我将尝试以某种方式获取标准引用,但这可能需要时间。