4

我想了解为什么 C++ 标准要求使用 '-D_WITH_BUG_' 编译时,中间的非最派生类不能调用虚拟基非默认构造函数,就像这段代码一样:

/*  A virtual base's non-default constructor is NOT called UNLESS 
 *  the MOST DERIVED class explicitly invokes it
 */

#include <type_traits>
#include <string>
#include <iostream>

class A
{
public:
    int _a;
    A():  _a(1)
    {
        std::cerr << "A() - me: " << ((void*)this) << std::endl;
    }
    A(int a): _a(a)
    {
        std::cerr << "A(a) - me:" << ((void*)this) << std::endl;
    }
    virtual ~A()
    {
        std::cerr << "~A" << ((void*)this) << std::endl;
    }
};

class B: public virtual A
{
public:
    int _b;
    B(): A(), _b(2)
    {
        std::cerr << "B() - me: " << ((void*)this) << std::endl;
    }
    B(int b) : A(), _b(b)
    {
        std::cerr << "B(b) - me: " << ((void*)this) << std::endl;
    }
    B(int a, int b): A(a), _b(b)
    {
        std::cerr << "B(a,b) - me: " << ((void*)this) << std::endl;
    }
    virtual ~B()
    {
        std::cerr << "~B" << ((void*)this) << std::endl;
    }
};

class C: public virtual B
{
public:
    int _c;
    C(): B(), _c(3)
    {
        std::cerr  << "C()" << std::endl;
    }
    C(int a, int b, int c)
    :
#ifdef _WITH_BUG_    
    B(a,b)
#else
    A(a), B(b)
#endif    
    , _c(c)
    {
        std::cerr  << "C(a,b) - me: " << ((void*)this) << std::endl;    
    }
    virtual ~C()
    {
        std::cerr << "~C" << ((void*)this) << std::endl;
    }  
};
extern "C"
int main(int argc, const char *const* argv, const char *const* envp)
{
    C c(4,5,6);
    std::cerr << " a: " << c._a  << " b: " << c._b << " c: " << c._c 
              <<  std::endl;
    return 0;
}

因此,当没有 -D_WITH_BUG_ 编译时,代码会打印:

$ g++ -I. -std=gnu++17 -mtune=native -g3 -fPIC -pipe -Wall -Wextra \
  -Wno-unused -fno-pretty-templates -Wno-register  \
  tCXX_VB.C -o tCXX_VB 
$ ./tCXX_VB
A(a) - me:0x7ffc410b8c10
B(b) - me: 0x7ffc410b8c00
C(a,b) - me: 0x7ffc410b8bf0
a: 4 b: 5 c: 6
~C0x7ffc410b8bf0
~B0x7ffc410b8c00
~A0x7ffc410b8c10

但是当使用 -D_WITH_BUG_ 编译时:

$ g++ -I. -std=gnu++17 -mtune=native -g3 -fPIC -pipe -Wall -Wextra \ 
  -Wno-unused -fno-pretty-templates -Wno-register \
  -D_WITH_BUG_ tCXX_VB.C -o tCXX_VB
$ ./tCXX_VB
A() - me: 0x7ffd7153cb60
B(a,b) - me: 0x7ffd7153cb50
C(a,b) - me: 0x7ffd7153cb40
a: 1 b: 5 c: 6
~C0x7ffd7153cb40
~B0x7ffd7153cb50
~A0x7ffd7153cb60

为什么这里必须忽略 B(int a, int b) 对 A(a) 的调用?我理解 C++ 标准要求它,但为什么呢?什么是理性?

如果我只实例化一个 B 对象: B b(4,5) ;这确实得到了正确的 b._a 值 4;但是如果 B 是 C 的子类: C c(4,5,6) C::a 最终为 1,则 IFF c 不会直接调用 A(a) 。因此,如果 B(a,b) 是子类对象,则 B(a,b) 的值与它是最派生对象时不同。这对我来说是非常混乱和错误的。有没有希望让足够多的人同意改变 C++ 标准?

4

3 回答 3

3

虚拟继承的全部目的是解决菱形问题。一旦你有了一个虚拟基类,你的层次结构看起来像这样:

  A
 / \
B   C
 \ /
  D

您需要知道何时构建A. 你不能先B构造它C然后立即覆盖它——你需要它只构造一次。好的,那我们什么时候可以这样做?最简单的选择就是:让最派生的类去做!所以当我们初始化 的B子对象时D,它不会初始化它的A子对象,因为B它不是最派生的类型。

在您的情况下,您的层次结构仍然是线性的:

A
|
B
|
C

但派生最多的类型 ,C必须初始化所有虚拟基 -AB. B不会初始化它的A子对象,原因与在复杂示例中没有初始化的原因相同。

于 2017-06-28T17:41:03.367 回答
1

这种行为是因为virtual base class. 由于 A 是虚拟基类,它由最派生的类构造。
您可以查看有关菱形继承问题关于类似问题的讨论,以了解为什么必须以这种方式。
首先了解虚拟基类是如何解决diamod shape问题的。
class A { ...}
class B: virtual public A {...}
class C: virtual public A {...}
class D: public B, public C {...}
当您将基类设置为虚拟时,将有一个基类对象。中间派生类对象都将引用同一个基类对象。即这里如果一个对象D被创建然后 B::A 和 C::A 都将引用同一个对象。这个单一对象是 B 和 C 的基类。因此,如果允许中间类构造基类对象,则有两个派生类来构造这个单一对象。通过赋予最派生类构建虚拟基类的责任来解决这种歧义。

于 2017-06-28T13:53:31.937 回答
1

您不太可能获得更改语言的任何支持。虚拟继承在多重继承场景中有用。

为什么这里必须忽略 B(int a, int b) 对 A(a) 的调用?

因为唯一的 A 子对象已经构建好了。构造函数不是一个普通的函数,你不能在任何地方调用它。

你可以写

C(int a, int b, int c)
    : A(a), B(a, b), _c(c)
    { ... }

这将给出B::B(int, int)传递给的参数的主体A::A(int)

于 2017-06-28T13:56:12.540 回答