如何避免在基类初始化程序中调用默认构造函数?

Posted

技术标签:

【中文标题】如何避免在基类初始化程序中调用默认构造函数?【英文标题】:How do I keep from calling default constructor in base class intializers? 【发布时间】:2019-04-03 00:33:24 【问题描述】:

我正在尝试构造Derived3,以便在初始化d2 时调用非默认构造函数。我原以为当d2 被初始化时,不会调用任何默认构造函数。使用此代码:

#include <string>
#include <iostream>

struct Base

    Base() : _message("Value initialized by default constructor")
    
        std::cout << "Base default constructor called" << std::endl;
    
    Base(std::string message) : _message(message)
    
    

    std::string     _message;
;

struct Derived1 : virtual public Base

    Derived1() : Base()
    
        std::cout << "Derived1 default constructor called" << std::endl;
    
    Derived1(std::string message) : Base(message)
       
    

;

struct Derived2 : virtual public Base

    Derived2() : Base()
    
        std::cout << "Derived2 default constructor called" << std::endl;
    
    Derived2(std::string message) : Base(message)
    
    
;

struct Derived3 : virtual public Derived1, virtual public Derived2

    Derived3() : Derived1(), Derived2()
    
        std::cout << "Derived3 default constructor called" << std::endl;
    
    Derived3(std::string message) : Derived1(message), Derived2(message)
    
    
;

int main()

    Derived3 d1 = Derived3();
    std::cout << d1._message << std::endl; // You get what you expect.

    Derived3 d2 = Derived3("Not initialized by default constructor");
    std::cout << d2._message << std::endl; // You get what you do not expect.

我原以为d2._message 会是"Not initialized by default constructor",而实际上它是"Value initialized by default constructor"。完整输出为:

Base default constructor called
Derived1 default constructor called
Derived2 default constructor called
Derived3 default constructor called
Value initialized by default constructor
Base default constructor called
Value initialized by default constructor

预期输出:

Base default constructor called
Derived1 default constructor called
Derived2 default constructor called
Derived3 default constructor called
Value initialized by default constructor
Not initialized by default constructor

为什么会发生这种情况以及如何实现预期的行为?

【问题讨论】:

如果您删除默认构造函数,编译器可能会在使用它们的地方出错。或者您可以添加一个断点,它也可能提示您。或者考虑删除Derived2Derived1 以使更多示例更小。 【参考方案1】:

当您虚拟继承一个基类时,在所有情况下,虚拟继承的基类都可以被认为是所谓的“最派生”类的直接超类。更改您的 Derived3 构造函数,如下所示:

Derived3(std::string message) : Derived1(message), Derived2(message),
                Base(message)

Base 实际上也是Derived3 的基类,因为它实际上是从Derived1(和Derived2)继承而来的。这就是虚拟继承。

如果您不希望 Base 被默认构造,您必须在此处自己调用适当的构造函数。

即使您没有明确声明 Derived3 继承自 Base,它实际上也继承了它,因此您可以从 Derived3 调用它的构造函数。

请注意,如果您将Derived4 声明为Derived3 的子类,则此处Base 的构造函数将不会被调用。 Derived4 将实际上继承 Base,并负责构建它。

当您拥有虚拟继承的类时,真正发生的事情是,您声明的 每个 构造函数可以被认为实际上导致了两个实际的构造函数,实际上:构造函数负责构造所有虚拟继承的类,而构造函数则不负责。你在上面声明的这个Derived3 构造函数:你实际上最终得到了两个构造函数。两个的价格:一个将构建Base,一个不会。将构造Base 的类在直接构造Derived3 时使用,是派生最多的类。第二个构造函数是相同的,只是它不会构造Base,并且如果Derived3 的子类被实例化,它就会被使用。您将其视为一个构造函数,但编译器做了更多工作,创建了其中两个,并确保在需要构造某些东西时使用正确的一个。

【讨论】:

以上是关于如何避免在基类初始化程序中调用默认构造函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在基类中将默认构造函数设为私有?

调用派生类的构造函数在基类的构造函数之前执行

C++中如何在子类的构造函数中调用基类的构造函数来初始化基类成员变量

如何在基类构造函数中使用派生类成员

基类构造函数的派生类成员初始化

如何在构造函数中注入 IEnumerable<ICustomRepository>,以便我可以决定在基类中使用哪一个?