继承到派生类的基构造函数

Base constructors inherited to derived class?

本文关键字:构造函数 派生 继承      更新时间:2023-10-16

我一直认为基类构造函数/析构函数/友元不是由派生类继承的。此链接证实了这一点:http://www.geeksforgeeks.org/g-fact-4/.

我还知道,我们可以在派生类初始化器列表的初始化列表中编写基类构造函数。

话虽如此:我今天试着检查一下我的技能。但我没能猜到这个程序的输出。

#include<iostream>
class A {
int x, y;
public:
A(int a = 0, int b = 0) : x(a), y(b) {
std::cout << "A ctor called" << std::endl;
}
void print_A() {
std::cout << "x = " << x << std::endl;
std::cout << "y = " << y << std::endl;
}
};
class B : public A {
int z;
public:
// I knew that A member can be initilized like this.
B(int a = 0, int b = 0, int c = 0) : z(a), A(b, c) {
std::cout << "C ctor called" << std::endl;
// I was not aware about that. 
A(b, c);
}
void print_B() {
std::cout << "z = " << z << std::endl;
}
};
int main() {
B b(1, 2, 3);
b.print_A();
b.print_B();
}

输出:

A ctor called
C ctor called
A ctor called
x = 2
y = 3
z = 1

几个问题:

  • 如果构造函数/解构造函数/友元不是从基继承的,那么类"B"如何能够访问类"A"的构造函数。

  • 你是怎么得到这个输出的?为什么已经调用了"A"的两个构造函数。

您的理解有误。此:

// I was not aware about that. 
A(b, c);

没有初始化B的A成员,它(至少在名义上)在构造函数的主体中创建了一个临时的、无名称的局部变量,有点类似于你说的:

A a(b, c);

A的构造函数是一个公共成员,所以任何东西都可以调用它

如果构造函数/解构造函数/友元不是从基继承的,那么类"B"如何能够访问类"A"的构造函数?

"未继承"并不意味着"派生类无法访问"。派生类当然可以引用基构造函数。B的构造函数做了两次:

  • 第一次访问在初始化列表中
  • 第二个访问位于B的构造函数的主体中;它创建了一个临时对象

继承构造函数意味着B的用户将能够访问B(int, int),而他们不能这样做*

它看起来像是一个构造函数调用。为什么它要创建一个临时对象?

考虑这个方法:

void foo(const A& a);

一种常见的方法是这样称呼它:

A a(1, 2);
foo(a);

但C++也允许您调用它,而无需在单独的行上创建A

foo(A(1, 2));

在这种情况下,C++创建一个临时对象,并将引用传递给foo

A(1, 2)

C++还通过调用其构造函数为您创建一个临时对象。

为什么已经调用了"A"的两个构造函数。

构造函数被调用两次;这就是你得到输出的原因。

*C++11的using机制允许您实现与构造函数继承非常相似的效果,前提是您遵循特定的规则。

每次调用A(b,c)时都会调用构造函数;

你在上打电话

B(int a = 0, int b = 0, int c = 0) : z(a), A(b, c) {

你在b构造函数中调用它

// I was not aware about that. 
A(b, c);

代码按预期工作

如果构造函数/解构造函数/友元不是从基继承的,那么类"B"如何能够访问类"A"的构造函数。

你是说在这几行?

// I was not aware about that. 
A(b, c);

它并不是你认为的"访问构造函数"。它只是在构造函数的主体中创建(并立即丢弃)一个匿名的临时A

您是如何获得此输出的?为什么已经调用了"A"的两个构造函数。

因为您创建了A的两个实例:B b的基类子对象和匿名临时对象。

这里有一个简单的实验来验证这一点,使用以下日志:

// in A
A(int a = 0, int b = 0) : x(a), y(b) {
std::cout << "A::A @" << static_cast<void*>(this) << std::endl;
}
~A() {
std::cout << "A::~A @" << static_cast<void*>(this) << std::endl;
}
// in B
B(int a = 0, int b = 0, int c = 0) : z(a), A(b, c) {
std::cout << "B::B @" << static_cast<void*>(this) << std::endl;
A(b, c);
}
~B() {
std::cout << "B::~B @" << static_cast<void*>(this) << std::endl;
}

我得到类似的输出

A::A @0xffec7054
B::B @0xffec7054
A::A @0xffec704c
A::~A @0xffec704c
x = 2
y = 3
z = 1
B::~B @0xffec7054
A::~A @0xffec7054

请注意,第二个A::A的实例地址与第一个不同(因此它是一个不同的对象),后面跟着一个A::~A(因为匿名临时会立即超出范围)。


备注:

  1. 如果你可以像最初建议的那样"调用构造函数",它看起来就像

    auto subobject = static_cast<A*>(this);
    new (subobject) A(b, c);
    

    这将是非常错误的。bA子对象是在B的构造函数主体开始之前,当它自己的构造函数完成时完全构造的。你不能在同一个空间里重新创建一个新对象,旧对象会发生什么?

    这可能看起来微不足道,但对于具有动态分配资源的对象来说,这将是一个严重的错误。这是不允许的。

  2. 您编写了初始化器列表: z(a), A(b, c),但应该注意,基类子对象将在初始化派生类成员之前构造。也就是说,这两件事将以与你所写相反的顺序发生。这不一定是一个错误,但值得知道。