继承的类数据成员

时间:2013-12-05 20:21:43

标签: c++ inheritance

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

2 个答案:

答案 0 :(得分:4)

建造者和破坏者是非常特殊的动物;事实上,标准将它们标识为“特殊成员函数”。

关于构造函数和析构函数的所有奇怪和独特的东西(例如,它们没有“名称”这一事实以及它们不是“继承”的事实)几乎与所有编程无关和程序员。以下是您需要了解的内容:


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


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

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

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的构造函数体开始之前,{strong>所有的Bar基类和成员变量都有已经实例化并初始化。这就是为什么除了默认构造函数之外为Foo指定一些构造函数的唯一方法是通过初始化列表 - 这反过来又是必须具有初始化列表的原因。基类没有可用的默认构造函数。


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

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

再次考虑上面的FooBarBar来自Foo,因此在某种意义上Bar 是A Bar。它不仅仅是Foo,还有Foo没有的东西;但它拥有所有Foo - ness。由于Bar对象部分属于Foo,因此必须初始化Foo - Foo。由于所有初始化ob对象都是通过构造函数完成的,因此必须调用Foo的构造函数。

因此,构造函数和析构函数在重载意义上不是继承的 - 但是将调用基类的构造函数。他们必须是。否则,Bar对象的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析构函数(隐式)被正确调用,但Foo析构函数不是 - 留下大量65K长的漏洞。

要解决此问题,我们需要virtual一个class Foo { public: std::string mS; virtual ~Foo() {} }; 析构函数:

virtual

这没什么用,但它做了一件重要的事情:它设置了多态性,这样当我们调用析构函数(隐式)时,将调用虚拟覆盖。

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

答案 1 :(得分:3)

没有。正如你所说,构造函数和析构函数不是继承的(顺便说一句,赋值运算符也不是)。如果它们是遗传的,那将是逻辑上的缺陷,对吧?构造函数(和析构函数)特定于该类;并且因为派生类通常具有特定的和新的,所以基础构造函数不足以初始化继承的类对象。

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

编译器将:

1)找到派生类的构造函数,其初始化程序列表最适合传递的参数,但不执行它;

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

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

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