继承的类数据成员

Inherited class data members

本文关键字:数据成员 继承      更新时间:2023-10-16

我有点困惑,我正在阅读这个C++构造函数/析构函数继承,所以它说构造函数和析构函数不是从基类继承到派生类的,而是在我创建派生对象时调用构造函数和析构函数。那么基类的构造函数和析构函数是继承类的数据成员吗?

构造函数和析构函数是非常特殊的动物;事实上,该标准将它们确定为"特殊成员职能"

构造函数和析构函数的所有奇怪和独特之处(例如,它们没有"名称",也没有"继承")实际上与几乎所有编程和程序员都无关。以下是您需要了解的内容:


构造函数和析构函数(显然)不是成员变量——它们是(特殊的)成员函数。


从基类派生时,将调用基类的一些构造函数哪个构造函数取决于您编写代码的方式。如果没有明确指定要调用的构造函数,则将调用默认构造函数。这种情况发生在派生对象的初始化列表中,即使您还没有编写初始化列表。

您可以通过初始化列表指定另一个构造函数,仅限:

class Bar : public Foo
{
public:
  Bar()
  :
    Foo (1, 2, 3)
  { 
  }
};

这里,Bar的初始化列表指定了Foo上的构造函数,该构造函数接受3个可从积分转换的参数。一个这样的构造函数可能是:

class Foo
{
public:
  Foo (int a, long b, unsigned c)
  :
    mA (a),
    mB (b),
    mC (c)
  {
  }
private:
  int mA;
  long mB;
  unsigned mC;
};

回到上面的Bar示例。在执行完初始化列表时,在Bar的构造函数主体启动之前,所有Bar基类和成员变量都已实例化和初始化。这就是为什么除了默认构造函数之外,为Foo指定某些构造函数的唯一方法是通过初始化列表——这也是为什么如果基类没有可用的默认构造函数,则必须具有初始化列表的原因。


问题:如果构造函数和析构函数不是继承的,那么为什么在实例化派生类型时调用它们?

答:因为构造函数是初始化对象的对象,并且派生类型与基类型有IS-a关系。

再次考虑上面的FooBarBar来源于Foo,因此在某种意义上BarISBar。它不仅仅是一个Foo,它有Foo没有的东西;但它具有所有的CCD_ 15性质。由于Bar对象在一定程度上是Foo,因此必须初始化该Foo。由于所有ob对象的初始化都是通过构造函数完成的,因此必须调用Foo的构造函数。

因此,构造函数和析构函数不是从重载的意义上继承的,而是基类的构造函数将被调用。它们必须是。否则,Bar对象的Foo性质永远不会成为。


当您从基类派生时,您通常想要做的事情是传递一个指向基类的指针。事实上,在许多情况下,您可能根本不需要指向派生类型的指针。在设计抽象接口时尤其如此。

当你这样做并且没有做好所有必要的准备时,会出现一个令人讨厌的问题。考虑:

class Foo
{
public:
  std::string mS;
};
class Bar : public Foo
{
public:
  long mArray[0xFFFF]; // an array of 65K longs
};
int main()
{
  Foo* thingy = new Bar;  // Totally OK
  // ... do stuff...
  delete thingy;  //  WHOOPS!  Memory leak!
}

在上面的delete调用中,我们通过基类指针进行删除。Foo析构函数(它是隐式的)被正确调用,但Bar析构函数没有被调用——留下了65K长的庞大数组。

为了解决这个问题,我们需要给Foo一个virtual析构函数:

class Foo
{
public:
  std::string mS;
  virtual ~Foo() {}
};

这并没有多大作用,但它做了一件关键的事情:它设置了多态性,这样当我们(隐式)调用析构函数时,就会调用虚拟覆盖。

这是设计类层次结构的关键步骤。如果你认为人们有可能传递指向基类的指针,那么你应该在基类中有一个virtual析构函数。事实上,我建议在几乎所有情况下都应该有一个virtual析构函数,即使你不认为人们会这样使用它。

否。正如您所说,构造函数和析构函数都不是继承的(顺便说一句,赋值运算符也不是)。如果他们被继承,在逻辑上是有缺陷的,对吧?构造函数(和析构函数)是特定于该类的;由于派生类通常有一些特定的和新的东西,基构造函数不足以初始化继承的类对象。

那么,为什么在创建子类的对象时调用基构造函数呢?每个派生对象都有一个子对象,这是父类的实际对象(我希望这句话不难理解)。

编译器将:

1) 查找派生类的构造函数,该派生类的初始值设定项列表最适合传递的参数,但不执行它

2) 执行基类的构造函数以创建子对象;

3) 执行派生类的构造函数,以创建实际对象。

我希望这是清楚的;您可以在上面的答案中找到更详细的解释:)