簡體   English   中英

為什么除非大多數派生基顯式調用它們,否則不調用虛擬基非默認構造函數?

[英]why are virtual base non-default constructors not called unless most-derived base explicitly invokes them?

我想了解WHY C ++標准的強制性要求,即在使用'-D_WITH_BUG_'進行編譯時,不能由中間NOT最高派生類調用虛擬基類非默認構造函數:

/*  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)是子類對象,則它的值與如果它是最派生對象的值是不同的。 這對我來說是非常混亂和錯誤的。 是否有希望讓足夠多的人同意在此基礎上更改C ++標准?

虛擬繼承的全部目的是解決鑽石問題 一旦有了虛擬基類,您的層次結構將如下所示:

  A
 / \
B   C
 \ /
  D

您需要知道何時構造A 您不能讓B構造它,然后C立刻覆蓋它-您需要將它構造一次。 好的,那我們什么時候可以做? 最簡單的選擇就是:讓最派生的類來做! 因此,當我們初始化DB子對象時,它將不會初始化其A子對象,因為B不是最派生的類型。

在您的情況下,您的層次結構仍然是線性的:

A
|
B
|
C

但最派生的類型C必須初始化所有虛擬基數AB 由於復雜示例中的原因, B不會初始化其A子對象。

此行為是由於virtual base class 由於A是虛擬基類,因此它是由派生程度最高的類構造的。
您可以檢查有關菱形繼承的問題以及有關類似問題的討論,以了解為什么必須采用這種方式。
首先了解虛擬基類如何解決diamod形狀問題。
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的基類。因此,如果允許中間類構造基類對象,則有兩個派生類可以構造該單個對象。 通過賦予派生最多的類構造虛擬基類的責任,可以解決這種歧義。

您不太可能獲得支持來更改語言。 虛擬繼承在多個繼承方案中有用。

為什么在這里必須忽略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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM