繁体   English   中英

C ++私有虚拟继承问题

[英]C++ private virtual inheritance problem

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

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

int main() {
    C c;
    return 0;
}

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

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)编译的代码”,但已经验证它与其他编译器的行为相同。

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

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

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-initializer-id命名(包括没有mem-initializer-list的情况,因为构造函数没有ctor-initializer)并且实体不是虚拟的抽象类的基类

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

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

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

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

如果编译器的默认构造函数只是在C查找类名A ,那么对于要初始化的子对象,它会有一个模糊的查找结果,因为找到了非虚拟A和虚拟A的类名。 如果您的代码意图不正确,我会说标准肯定需要澄清。


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

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

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

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

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

virtual_base -> A::~A();

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

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

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

始终从最派生的类(此处为C)初始化虚拟基类。 编译器必须检查构造函数是否可访问(即我在g ++ 3.4中遇到错误

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 )。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM