有没有在 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】:

一个可能的情况是AB 都没有用户声明的构造函数并且B 的实例正在值初始化

AB 都隐式声明了不会在此初始化中使用的构造函数。

类似地,如果 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++ 中不调用超类的构造函数的时候?的主要内容,如果未能解决你的问题,请参考以下文章

什么时候需要显式调用超类构造函数?

C++中派生类的构造函数怎么显式调用基类构造函数?

C++:对象和类|| 类的构造函数与析构函数

C++:对象和类|| 类的构造函数与析构函数

用C++设计一个不能被继承的类(转)

C++中,子类会继承父类的虚函数表!对于父类的析构函数(虚函数) 也会继承吗?