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

时间:2014-01-28 01:52:34

标签: c++ constructor base-class

我觉得这是不可能的,例如: Calling the constructor of the base class after some other instructions in 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。

4 个答案:

答案 0 :(得分:12)

子类构造函数内部的调用不是调用基类构造函数,而是创建一个类型为Person的临时,未命名和新对象。它将在构造函数退出时被销毁。为了澄清,您的示例与执行此操作相同:

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

答案 1 :(得分:6)

您无法从子构造函数的主体调用它,但您可以将其放入初始化列表中:

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

当然,调用父项的默认构造函数是没有用的,因为这会自动发生。如果需要将参数传递给构造函数,它会更有用。

您无法从正文中调用构造函数的原因是因为C ++保证父代将在子构造函数启动之前完成构造。

答案 2 :(得分:6)

以下是“Accelerated C ++”的摘录: “派生的物体由以下构成:
1.为整个对象分配空间(基类成员以及派生类成员);
2.调用基类构造函数初始化对象的基类部分;
3.按照构造函数初始值设定项的指示初始化派生类的成员;
4.执行派生类构造函数的主体,如果有的话。“

总结答案和注释:在上面的#2必须在#4之前的意义上,从子类的构造函数体调用基类的构造函数是不可能的。 但是我们仍然可以在派生的构造函数体中创建一个基础对象,从而调用基础构造函数。它将是一个与使用当前执行的派生构造函数构造的对象不同的对象。

答案 3 :(得分:0)

这个问题的答案虽然通常在技术上是正确的和有用的,但请不要大张旗鼓。而且总体情况与看起来有些不同:)

  1. 总是会调用基类的构造函数 ,否则在派生类的构造函数的主体中,您将拥有部分构造的对象,因此无法使用。您可以通过 option 为基类构造函数提供参数。这不会“调用”它:无论如何,它都会被调用,您只需将一些额外的参数传递给它即可:

    // 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 ++语法具有一个怪异的名称,并且不辜负这个名称,因为这很少执行-通常仅在库/基础代码中执行。它称为 placement new ,不,它与内存分配无关-这是在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通常会导致崩溃(例如空指针访问),但是很多UB都是无声的杀手。因此,通过使某些语法晦涩难懂来编写错误代码变得更加困难,这是一种语言的理想特性。

  3. 问题中的“第二个选项”与构造函数“ calls”无关。创建值为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,我们注意到{{ 1}}是一个表达式!

    因此,在您的情况下,type()语句是无操作的。在以发布模式编译的代码中,不会为它生成任何机器指令,因为无法观察到该语句的效果(对于特定的Person();类而言),因此,如果没有人可以听到树倒下,那么树就不必首先存在。这就是C ++编译器如何优化内容的方法:他们做了很多工作来证明(从数学意义上来说,形式上是正式的)任何代码的效果是否都是不可观察的,如果是,则将这些代码视为无效代码并删除。