为什么派生类型的基类构造函数会被调用?

问题描述 投票:0回答:6
#include <iostream>
#include <stdio.h> 
using namespace std;

// Base class
class Shape 
{
   public:
      void setWidth(int w)
      {
         width = w;
      }
      void setHeight(int h)
      {
         height = h;
      }
      Shape()
      {
    printf("creating shape \n");
      }
      Shape(int h,int w)
      {
     height = h;
         width = w;
         printf("creatig shape with attributes\n");
      } 
   protected:
      int width;
      int height;
};

// Derived class
class Rectangle: public Shape
{
   public:
      int getArea()
      { 
         return (width * height); 
      }
      Rectangle()
      {
     printf("creating rectangle \n");
      }
      Rectangle(int h,int w)
      {
     printf("creating rectangle with attributes \n");
     height = h;
         width = w;
      }
};

int main(void)
{
   Rectangle Rect;

   Rect.setWidth(5);
   Rect.setHeight(7);

   Rectangle *square = new Rectangle(5,5);
   // Print the area of the object.
   cout << "Total area: " << Rect.getArea() << endl;

   return 0;
}

程序的输出如下

creating shape 
creating rectangle 
creating shape 
creating rectangle with attributes 
Total area: 35

在构造两个派生类对象时,我发现首先调用的始终是基类的默认构造函数。是否有一个原因?这就是像Python这样的语言坚持显式调用基类构造函数而不是像C++那样隐式调用的原因吗?

c++ object inheritance
6个回答
116
投票

对此的简短回答是,“因为这就是 C++ 标准所指定的”。

请注意,您始终可以指定与默认值不同的构造函数,如下所示:

class Shape  {

  Shape()  {...} //default constructor
  Shape(int h, int w) {....} //some custom constructor


};

class Rectangle : public Shape {
  Rectangle(int h, int w) : Shape(h, w) {...} //you can specify which base class constructor to call

}

仅当您未指定调用哪一个时,才会调用基类的默认构造函数。


27
投票

除非您在派生类中显式调用另一个构造函数,否则将调用默认的类构造函数。语言指定了这一点。

Rectangle(int h,int w):
   Shape(h,w)
  {...}

将调用其他基类构造函数。


5
投票

构造对象时,总是先构造基类子对象,因此,先调用基类构造函数,然后调用派生类构造函数。原因是派生类对象包含从基类继承的子对象。您始终需要调用基类构造函数来初始化基类子对象。我们通常在派生类的成员初始化列表上调用基类构造函数。如果没有显式调用基类构造函数,编译时将调用基类的默认构造函数来初始化基类子对象。但是,对默认构造函数的隐式调用不一定始终有效(例如,如果基类定义了一个没有参数就无法调用的构造函数)。

当对象超出作用域时,会先调用派生类的析构函数,然后调用基类的析构函数。


3
投票

为什么要调用基类的默认构造函数?事实证明情况并非总是如此。基类的任何构造函数(具有不同的签名)都可以从派生类的构造函数中调用。在你的情况下,默认构造函数被调用,因为它没有参数,所以它是默认的。

创建派生类时,构造函数的调用顺序在层次结构中始终为 Base -> Derived。如果我们有:

class A {..}
class B : A {...}
class C : B {...}
C c;

创建 c 时,首先调用 A 的构造函数,然后调用 B 的构造函数,最后调用 C 的构造函数。

为了保证这个顺序,当调用派生类的构造函数时,它总是在派生类的构造函数执行任何其他操作之前调用基类的构造函数。因此,程序员可以在派生类构造函数的唯一初始化列表中手动调用基类构造函数,并使用相应的参数。例如,在下面的代码中,Derived 的默认构造函数将调用 Base 的构造函数 Base::Base(int i) 而不是默认构造函数。

Derived() : Base(5)
{      
}

如果派生类构造函数的初始化列表中没有调用此类构造函数,则程序将假定基类构造函数不带参数。这就是调用没有参数的构造函数(即默认构造函数)的原因。


2
投票

在C++中,编译器总是确保对象层次结构中的函数被成功调用。这些函数是构造函数和析构函数,对象层次结构意味着继承树。

根据这个规则,我们可以猜测,即使我们不实现它,编译器也会为继承层次结构中的每个对象调用构造函数和析构函数。为了执行这个操作,编译器会为我们合成未定义的构造函数和析构函数,我们将它们命名为默认构造函数和析构函数。然后,编译器将调用基类的默认构造函数,然后调用派生类的构造函数。

在您的情况下,您不会调用基类构造函数,但编译器会通过调用基类的默认构造函数来为您执行此操作,因为如果编译器没有执行此操作,则您的派生类(示例中的 Rectangle)将不完整,并且可能会导致灾难,因为也许您将在派生类中使用基类的某些成员函数。因此,为了安全起见,编译器始终需要所有构造函数调用。


0
投票

像这样想象一下:当您的子类从超类继承属性时,它们不会神奇地出现。您仍然需要构造该对象。因此,您调用基本构造函数。想象一下,如果您的类继承了一个变量,您的超类构造函数将其初始化为一个重要值。如果我们不这样做,您的代码可能会失败,因为变量未初始化。

© www.soinside.com 2019 - 2024. All rights reserved.