C++私有虚拟继承问题

Oak*_*Oak 20 c++ inheritance encapsulation virtual-inheritance private-inheritance

在下面的代码中,似乎类C无法访问A的构造函数,这是因为虚拟继承所必需的.然而,代码仍然编译和运行.它为什么有效?

class A {};
class B: private virtual A {};
class C: public B {};

int main() {
    C c;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

class A {
public:
    A(int) {}
};
class B: private virtual A {
public:
    B() : A(3) {}
};
Run Code Online (Sandbox Code Playgroud)

然后

class C: public B {};
Run Code Online (Sandbox Code Playgroud)

会(意外地)编译,但是

class C: public B {
public:
    C() {}
};
Run Code Online (Sandbox Code Playgroud)

不会像预期的那样编译.

使用"g ++(GCC)3.4.4(cygming special,gdc 0.12,使用dmd 0.125)编译的代码",但已经验证它与其他编译器的行为相同.

Kir*_*sky 14

根据C++核心问题#7类具有虚拟私有基础无法派生.这是编译器中的一个错误.

  • +1用于引用相关文档.-1因为文档错误. (2认同)

Joh*_*itb 6

对于第二个问题,可能是因为您没有隐含地定义它.如果仅隐式声明构造函数,则不会出现错误.例:

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;
Run Code Online (Sandbox Code Playgroud)

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

class A {};
class B: private virtual A {};
class C: public B { C(): ::A() { } }; // don't use B::A
Run Code Online (Sandbox Code Playgroud)

也许此时标准尚未明确.我们得看看.


代码似乎没有任何问题.此外,表明代码有效.(虚拟)基类子对象是默认初始化的 - 没有文本暗示类名的名称查找在范围内C.这是标准所说的:

12.6.2/8 (C++ 0X)

如果给定的非静态数据成员或基类没有由mem-initializer-id命名(包括没有mem-initializer-list的情况,因为构造函数没有ctor-initializer)并且实体不是虚拟的抽象类的基类

[...]否则,实体默认初始化

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

考虑这个代码,肯定是为了是有效的,但会失败,如果该进行(见12.6.2/4C++ 0x中)

struct A { };
struct B : virtual A { };
struct C : B, A { };
C c;
Run Code Online (Sandbox Code Playgroud)

如果编译器的默认构造函数仅查找类名A里面C,它会针对哪些子对象进行初始化,因为这两种非虚暧昧的查找结果A与虚拟A的类名被发现.如果您的代码意图不正确,我会说标准肯定需要澄清.


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

调用所有析构函数,就好像它们是使用限定名称引用一样,即忽略更多派生类中的任何可能的虚拟覆盖析构函数.

这可以通过两种方式解释:

  • 调用A :: ~A()
  • 调用:: A :: ~A()

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

virtual_base -> A::~A();
Run Code Online (Sandbox Code Playgroud)

然后我们将直接找到虚拟基类'类名作为公共名称,因为我们不必通过派生类'范围并将名称查找为不可访问.同样,推理也是类似的.考虑:

struct A { };
struct B : A { };
struct C : B, A {
} c;
Run Code Online (Sandbox Code Playgroud)

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

  • *"很多事情都不言而喻."*我多年来一直在使用这个规范,如果我不知道它是如何工作的,那么它确实*不会*不言而喻(如果你不同意,那么就提起来)关于你不同意和为什么这样做,而不仅仅是简单的声明如"没有名字查询问题"/"许多事情不言而喻".我很抱歉,你的评论看起来像是一个拖着一个认为他的人的行为比其他人更聪明). (6认同)
  • @curious,因为这个答案已经被*显然只是看了你的评论的人再次投票*,我想在你上次评论的第一部分给你11p4的C++ 11:"访问控制统一应用于所有名字,无论名称是从声明或表达中引用的." FWIW,名称的引入始终是一个声明.所以"使用的声明"等同于"使用的名称"("声明"的含义是双重的.一个是指句法结构,另一个是指名称的引入). (5认同)
  • @curiousguy:由于C++语言是由其规范定义的,因此不应该"忘记规范",也不要依赖"直觉"来实现细节.事实是规范*是*事实. (3认同)
  • 请在聊天中进行讨论.谢谢! (2认同)