Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/145.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 虚拟继承对构造函数的影响_C++_Inheritance_Multiple Inheritance_Virtual Inheritance - Fatal编程技术网

C++ 虚拟继承对构造函数的影响

C++ 虚拟继承对构造函数的影响,c++,inheritance,multiple-inheritance,virtual-inheritance,C++,Inheritance,Multiple Inheritance,Virtual Inheritance,我已经开始学习虚拟继承(以及它如何解决从具有相同父类的两个父类派生类的问题)。为了更好地理解其背后的机制,我举了一个例子: class A { public: A(string text = "Constructor A") { cout << text << endl; } }; class B: public A { public: B(): A("A called from B") { cout << "Constructor B"

我已经开始学习虚拟继承(以及它如何解决从具有相同父类的两个父类派生类的问题)。为了更好地理解其背后的机制,我举了一个例子:

class A {
public: 
    A(string text = "Constructor A") { cout << text << endl; }
};

class B:  public A {
public:
    B(): A("A called from B") { cout << "Constructor B" << endl; }
};

class C : virtual public A {
public:
    C() : A("A called from C") { cout << "Constructor C" << endl; }
};

class D :  public B,  public C {
public: 
    D() { cout << "Constructor D" << endl; }
};
困扰我的是,为什么有构造函数A“表示它不是由
类B
C
调用的。为什么在构造函数C之前没有从C调用A呢。对于后者,我知道它与
类C
是虚拟派生的有关,因此我猜它不会再次调用
构造函数A
,因为
类A
中的对象已经形成(事实上是两次)

编辑:

大体上,我只创建一个对象类型D

int main() {
  D d;
}
问题 您的代码忘记了什么,并且在
D
对象中仍然有两个
A
对象。您可以通过向
a
添加公共
int test
成员来验证此声明,并尝试以下代码。您将获得两个不同的地址:

D d;
cout << &d.B::test <<endl;   // the non virtual A subobject of B
cout << &d.C::test <<endl;   // the virtual A subobject of C
上面的代码片段将按预期工作,您甚至可以在不出现任何歧义错误的情况下处理
d.test

编辑:精致的建筑

C++规则要求每个对象具有一个虚拟<代码>一个子对象,以提供一个构造函数,用于<代码> A<代码>。在您的情况下,

D
应该提供虚拟
A
的构造

由于没有显式构造函数,
D
将查找默认构造函数
a
。它会找到一个,因为您为
a
构造函数提供了一个默认参数。这是误导,因为它告诉你“一个构造函数”,而实际上是
D
使用它

如果删除此默认参数,代码将不再编译。然后您需要这样的东西才能正确构造
A

class A {
public: 
    int test; 
    A(string text) { cout << "A is constructed: "<<text << endl; }
};

class D :  public B,  public C {
public: 
    D() : A("Mandatory, if there is no default consructor") { cout << "Constructor D" << endl; }
};
A类{
公众:
智力测验;

(字符串文本){cout首先,由于
B
非虚拟地从
A
派生而来,
D
最终得到两个
A
子对象(就好像您根本没有使用
虚拟
继承一样)

非虚拟的
A
B()
构造(因此
“A从B调用”
),而虚拟的在构造时打印
“构造函数A”

这是因为虚基(可能是间接基)的构造函数总是由派生最多的类(
D
)的构造函数调用,而不是由任何中间基(
C
)的构造函数调用

这意味着
C()
中的
:A(“从C调用的A”)
被忽略(因为你不是在构建一个独立的
C
)。而且
D()
在其成员初始值设定项列表中没有提到
A
,虚拟
A
是在没有任何参数的情况下构建的(就像通过
:A()


另外,值得一提的是,所有虚拟基地都是非虚拟基地


因此,您的代码保证在从B调用
A之前打印
构造函数A

当一个类型有一个虚拟基时,虚拟基是从派生类型最多的构造函数构造的。因此,从D的构造函数调用“构造函数A”

更详细一点:

#include <iostream>

struct base {
    base() { std::cout << "base()\n"; }
    base(int) { std::cout << "base(int)\n"; }
};

struct i1 : virtual base {
    i1() : base(0) { std::cout << "i1()\n"; }
};

struct i2 : virtual base {
    i2() : base(1) { std::cout << "i2()\n"; }
};

struct d : i1, i2 {
};
这里的输出是

[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base()
i1()
i2()
[temp]$ 
请注意,
i1
i2
的构造函数没有构造
子对象。编译器注意:基只应初始化一次,而
d
的构造函数就这样做了

如果希望对基本对象进行不同的初始化,请在构造函数中写入:

d::d() : base(2) {}
将其添加到类
d
中会产生以下输出:

[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base(int)
i1()
i2()
[temp]$ 

你能给我们看看你的
main()
您在哪里创建这些类的实例?@Hawky当然可以,但正如我所说,我只创建了一个对象类型D,就是这样。对不起,我漏掉了这一行,这是我的错。如果您试图做的是尽可能简洁地向基类添加一组预先存在的功能,那么还可以考虑作为虚拟继承的替代方法。简单地说,您可以可以将
struct FooableBase:virtualbase{…}
替换为
template struct Fooable:BaseT{…}
,然后将
struct FooBar:FooableBase,BarableBase{…}
替换为
struct FooBar:Fooable{…}
。我发现模板方法更容易使用。@alterigel这看起来是一种非常干净的方法。我一定会看一看,谢谢。我大部分时候都意识到了这一点。但困扰我的是“构造器a”来自哪里,如果它是从类B或类C调用的,它会说“a从B/C调用”,但它只是说“构造者A”就像一个没有参数的构造函数被调用。@ DZI抱歉,这是因为默认参数。参见我的编辑另一个原因C++规则是因为虚拟继承有额外的间接级别。遵循“你不为你不使用的东西付费”的规则,C++有选择的“支付”。“用于虚拟继承的额外开销,而不是默认值。如果我理解正确,初始化将从类C开始,该类实际上是从A派生的,因此将从该类调用不带参数的构造函数。然后初始化将以正常方式继续使用X(使用参数文本调用构造函数A,然后使用构造函数X)。然后初始化将移动到Y并调用其构造函数,然后最后将调用D的构造函数?@dzi,什么是
X
Y
?@dzi否,初始化从最派生的类开始,
D
。它构造所有(可能是间接的)虚拟基址(
A
),然后是所有直接非虚拟基址(
B
(依次构造非虚拟
A
)和
C
(不构造任何
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base()
i1()
i2()
[temp]$ 
d::d() : base(2) {}
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base(int)
i1()
i2()
[temp]$