从子类的构造函数主体调用基类的构造函数

Calling a constructor of the base class from a subclass' constructor body

本文关键字:构造函数 基类 调用 主体 子类      更新时间:2023-10-16

我的印象是不可能,例如:在C 中的其他一些说明之后,调用基类的构造函数
但是以下程序运行并产生了两行"构造者":

#include <iostream>
class Person
{
public:
    Person() 
    { 
        std::cout << "Constructor Person" << std::endl; }
    };
class Child : public Person
{
public:
    Child() 
    { 
        c = 1; 
        Person(); 
    }
    int c;
};
int main() 
{
    Child child;
    return 0;
}

第一个是默认构造函数的隐式调用,这很明显。第二个 - 这意味着标题中描述的动作是合法的吗?我使用Visual C 2010。

儿童类构造函数中的调用不是调用基类构造函数,它正在创建一个临时,未命名和新对象类型的人。当构造函数退出时,它将被破坏。要澄清,您的示例与这样做是相同的:

Child() { c = 1; Person tempPerson; }

除非在这种情况下,临时对象具有名称。

,如果您对示例进行一些修改,您可以看到我的意思:

class Person
{
public:
    Person(int id):id(id) { std::cout << "Constructor Person " << id << std::endl; }
    ~Person(){ std::cout << "Destroying Person " << id << std::endl; }
    int id;
};
class Child : public Person
{
public:
    Child():Person(1) { c = 1; Person(2); }
int c;
};
int main() {
Child child;
Person(3);
return 0;
}

这会产生输出:

Constructor Person 1
Constructor Person 2
Destroying Person 2
Constructor Person 3
Destroying Person 3
Destroying Person 1

以下是"加速C "的摘录:"派生的对象是由:
构造的1.为整个对象分配空间(基类成员以及派生的类成员);
2.调用基础级构造函数以初始化对象的基类部分;
3.根据构造函数初始化器指示的派生类成员的初始化;
4.执行派生级构造函数(如果有)。"

总结答案和评论:从子类的构造体主体中呼叫基类的构造函数是不可能的,因为上面的#2必须先于#4。但是我们仍然可以在派生的构造体主体中创建一个基本对象,从而调用基本构造函数。它将是一个与当前执行的派生构造函数构建的对象不同的对象。

您无法从子构建器的正文中调用它,但是您可以将其放入初始化列表中:

public:
    Child() : Person() { c = 1; }

当然,请致电父级的默认构造函数,因为这将自动发生。如果您需要将参数传递给构造函数,则更有用。

您无法从身体调用构造函数的原因是,C 保证父母将在子构建器启动之前完成构造。

这个问题的答案通常在技术上是真实且有用的,但不要给出大图。大局与看起来有些不同:)

  1. 基类的构造函数是始终调用的,否则在派生类的构造函数的主体中,您将拥有部分构造的,因此可以使用不可用的对象。您有向基类构造函数提供参数的选项。这不是"调用"它:无论如何它都会被调用,您只需向其传达一些额外的论点:

    // Correct but useless the BaseClass constructor is invoked anyway
    DerivedClass::DerivedClass() : BaseClass() { ... }
    // A way of giving arguments to the BaseClass constructor
    DerivedClass::DerivedClass() : BaseClass(42) { ... }
    
  2. c 语法对明确调用构造函数具有怪异的名称,并且要符合此名称,因为这很少做 - 通常仅在库/基础代码中。它称为放置新,不,它与内存分配无关 - 这是在C 中明确调用构造函数的怪异语法:

    // This code will compile but has undefined behavior
    // Do NOT do this
    // This is not a valid C++ program even though the compiler accepts it!
    DerivedClass::DerivedClass() { new (this) BaseClass(); /* WRONG */ }       
    DerivedClass::DerivedClass() { new (this) BaseClass(42); /* WRONG */ }
    // The above is how constructor calls are actually written in C++.
    

    因此,在您的问题中,这是您的意思,但不知道:)我想象这种怪异的语法是有帮助的(例如,Pascal/delphi)可以编写许多看似有效的代码,这些代码将以各种方式完全破坏。未定义的行为不是崩溃的保证,这就是问题所在。肤浅/明显的UB通常会导致崩溃(例如NULL指针访问),但是很多UB都是沉默的杀手。因此

  3. "第二选项"在问题中,与构造函数无关。创建BaseClass对象值的默认构造实例的C 语法是:

    // Constructs a temporary instance of the object, and promptly
    // destructs it. It's useless.
    BaseClass();
    // Here's how the compiler can interpret the above code. You can write either
    // one and it has identical effects. Notice how the scope of the value ends
    // and you have no access to it.
    {
      BaseClass __temporary{};
    }
    

    在c 中,对象实例的构造概念是无所不在的:您一直都在做,因为语言语义将对象的存在等同于已构造的对象。因此您也可以写:

    // Constructs a temporary integer, and promptly destructs it.
    int();
    

    整数类型的对象也是构造和破坏的 - 但是构造函数和破坏者很微不足道,因此没有开销。

    请注意,这种方式的构建和破坏并不意味着任何堆的库。如果编译器确定必须实际实现一个实例(例如,由于构造或破坏的可观察副作用),则实例是一个临时对象,就像在表达式评估中创建的临时性一样 - A -HA,我们注意到type()是表达!

    因此,在您的情况下, Person();语句是一个否。在发行模式中编译的代码中,没有生成机器说明,因为没有办法观察此语句的效果(对于特定Person类的情况),因此,如果没有人可以听到树的掉落,然后,这棵树不需要首先存在。这就是C 编译器优化内容的方式:他们做很多工作以证明(从数学意义上是形式上的)是否可能无法观察到任何代码的效果,如果这样,该代码被视为死亡代码并删除。

是的,我知道这已经一岁了,但我找到了一种方法。这可能不是最好的做法。例如,从派生的类构造函数中销毁基类实例听起来像是灾难的食谱。您可以跳过destructor步骤,但是如果基类构造函数进行任何分配,这可能会导致内存泄漏。

class Derived : public Base
{
public:
   Derived()
   {
       // By the time we arrive here, the base class is instantiated plus 
       // enough memory has been allocated for the additional derived class stuff.
       // You can initialize derived class stuff here
       this->Base::~Base(); // destroy the base class
       new (this) Base(); // overwrites the base class storage with a new instance
   }
};