有没有在 C++ 中不调用超类的构造函数的时候?
Posted
技术标签:
【中文标题】有没有在 C++ 中不调用超类的构造函数的时候?【英文标题】:Is there a time when a superclass's constructor is not called in C++? 【发布时间】:2011-04-27 21:42:38 【问题描述】:这个问题让我接受了采访。如果 B 是 A 的子类。构造B的时候,有没有调用A的构造函数的时候?
编辑:我告诉面试官我想不出这种情况,因为我认为只有在构造子类之前正确构造超类才有意义。
【问题讨论】:
只是出于好奇,你的答案是什么? 我想知道哪些地方仍然使用 C++ 的边缘案例琐事作为面试问题的一种形式。如果答案是肯定的,那么在编码的什么情况下知道这个问题的答案很重要。 @Andy Finkenstadt,这可能有助于淘汰自称是“专家”而不配得上这个词的人。 @Mark,我编辑了这个问题。如果没有办法这样做,那么我认为面试官可能正在查看我的思维过程。 如果这与思维过程有关,我有一些有趣的答案。在下面检查。 【参考方案1】:一个可能的情况是A
和B
都没有用户声明的构造函数并且B
的实例正在值初始化。
A
和 B
都隐式声明了不会在此初始化中使用的构造函数。
类似地,如果 A
没有用户声明的构造函数,但出现在 B
的构造函数的成员初始化列表中,但初始化程序为空,则 A
将在以下情况下被值初始化使用B
的这个构造函数。同样,因为A
没有用户声明的构造函数,值初始化不使用构造函数。
【讨论】:
从问题的上下文中我会想到A
有一个构造函数,但这是一个巧妙的漏洞。
@Mark: A 确实有一个构造函数。它没有用户定义的构造函数:-)
我没有意识到在最后一种情况下(你的最后一段)A
如果在 B
构造函数的初始化列表中存在一个空初始化器,它将被严格地初始化值。事实上,如果有的话,我会更早地假设这样的存在是一个显式的默认构造函数调用。你知道的越多……
value-initialized 是什么意思?我猜这意味着隐式生成的复制构造函数,它也是构造函数,它被调用(不一定通过call
asm 指令或其他东西,但它是根据语言语义调用的)。
@Ben:行为不一样。使用值初始化,任何 int
成员都设置为 0。默认生成的构造函数不会(必然)这样做。【参考方案2】:
我想您可以在为 B 的初始化列表中的 A 的非默认构造函数生成参数时做一些引发异常的事情?
您可以在下面看到 A 的构造函数永远不会被调用,因为在为其生成参数时发生了异常
#include <iostream>
using namespace std;
int f()
throw "something"; // Never throw a string, just an example
class A
public:
A(int x) cout << "Constructor for A called\n";
;
class B : public A
public:
B() : A(f())
;
int main()
try
B b;
catch (const char* ex)
cout << "Exception: " << ex << endl;
【讨论】:
但是 B 的构造也永远不会发生。 @unapersson:B
的构造已开始(但不返回)。所以问题的条件都满足了。
@JohnB:出色的答案。起初我没有注意到这一点。
我认为技术上B
的构建还没有开始; A
的构造是一种前置条件。这仍然是一个有趣的边缘案例。
@Mark Ransom:“某种先决条件”这不是先决条件。这是施工过程中必不可少的一部分。如果您在程序中的任何地方使用特定的构造函数(例如基/成员的初始化、简单对象的初始化、在某些表达式中创建临时对象、新表达式),那么您总是指所有构造步骤——而不仅仅是执行构造函数的主体。【参考方案3】:
虚拟继承。
struct B ...;
struct D1 : virtual B ...;
struct D2 : virtual B ...;
struct Child : D1, D2 ...;
通常构造函数B()
应该被调用两次,但它只会被调用一次。
【讨论】:
这是我要给出的例子。通常B
的构造发生在D1
的构造过程中,但是对于虚拟继承,B
的构造发生在Child
的构造过程中,D1::D1
不会调用其基类构造函数。
我也考虑过这个,但是拒绝了。基类构造函数仍然被调用,它满足问题标题中的查询。来自 Q 的文本 - “在构造 B 时,是否存在不调用 A 的构造函数的时间?” - 这有点模棱两可,因为不清楚“构建时”是否/如何对所考虑的时间范围施加限制。无论如何都值得列出,充实问题空间....
@Tony,这里的基类应该是为正常继承而构建的;但是使用virtual
继承,它只构建一次。因此,至少有一个派生类正在失去其基类构造。
iammilind:这种观点比考虑在派生类之间共享基类更有说服力吗?【参考方案4】:
在A完全构造之前,B的构造甚至无法开始,所以答案是否。
【讨论】:
我猜你的意思是在A完全构造之前没有输入B构造函数体,这是真的。但是,B 对象的构造肯定在第一个初始化列表表达式执行之前就开始了。所以你错了。 我相信创建特定对象的第一步是创建其所有子对象,包括超类型。最后的步骤之一实际上是进入它的构造函数。 @Serge Dundich,标准很好地指定了构建顺序。也许您对“构造”的定义过于宽松——例如,不包括为对象保留内存。 “也许你使用的定义太松散了” 不是真的。确实不包括保留内存,但包括构造基类和非静态成员。执行构造函数的 boby 只是最后一步。对象的构造是在您使用placement new 时发生的一切(它就像纯构造- 没有内存分配)。【参考方案5】:如果 B 在 A 之前构造了第二个超类,那么将有一段时间 A 的构造函数尚未被调用,尽管它会被调用。
【讨论】:
以上是关于有没有在 C++ 中不调用超类的构造函数的时候?的主要内容,如果未能解决你的问题,请参考以下文章