为什么不继承构造函数?


33

我对如果构造函数是从基类继承而可能出现的问题感到困惑。Cpp Primer Plus说,

构造函数与其他类方法的不同之处在于它们创建新对象,而其他方法则由现有对象调用这是构造函数不被继承的原因之一。继承意味着派生的对象可以使用基类方法,但是对于构造函数,该对象直到构造函数完成工作后才存在。

我了解在对象构造完成之前调用了构造函数。

如果子类继承(父类是指子类能够覆盖父类方法等,而不仅仅是访问父类方法),则父构造函数会导致问题吗?

我知道没有必要从代码中显式调用构造函数(尚不知道。),而在创建对象时除外。即使这样,您也可以使用某种机制来调用父构造器(在cpp中,使用::或使用member initialiser list,在java中使用super)。在Java中,在第一行有一个强制调用它,我知道这是要确保首先创建父对象,然后再进行子对象构造。

它可能会覆盖它。但是,我无法提出可能造成问题的情况。如果孩子确实继承了父构造函数,那会出错吗?

这样只是为了避免继承不必要的功能。还是还有更多呢?


使用C ++ 11并不能真正加快速度,但是我认为其中存在某种形式的构造函数继承。
扬尼斯

3
请记住,无论您在构造函数中执行什么操作,都必须注意析构函数。
Manoj R

2
我认为您需要定义“继承构造函数”的含义。所有答案似乎都对他们认为“继承构造函数”的含义有所不同。我认为它们都不符合我的最初解释。“继承意味着派生对象可以使用基类方法”上方的描述“在灰色框中”表示构造函数是继承的。派生对象肯定会并且确实会使用基类构造函数方法,总是。
Dunk

1
感谢您对继承构造函数的说明,现在您可以获得一些答案。
Dunk

Answers:


41

C ++中不能正确继承构造函数,因为派生类的构造函数需要执行基类构造函数不必执行且不知道的其他操作。这些附加操作是对派生类的数据成员的初始化(并且在典型的实现中,还将vpointer设置为引用派生类vtable)。

当构造一个类时,总是需要发生许多事情:基类的构造函数(如果有)和直接成员需要被调用,并且如果有任何虚函数,则必须正确设置vpointer 。如果您没有为您的类提供构造函数,则编译器创建一个执行所需操作而不执行其他操作的构造函数。如果确实提供了构造函数,但是错过了一些必需的操作(例如,某些成员的初始化),则编译器将自动将缺少的操作添加到构造函数中。这样,编译器确保每个类至少具有一个构造函数,并且每个构造函数都完全初始化其创建的对象。

在C ++ 11中,引入了一种“构造函数继承”形式,您可以在其中指示编译器为您生成一组构造函数,这些构造函数采用与基类中的构造函数相同的参数,并将这些参数转发给基类。
尽管正式将其称为继承,但实际上并非如此,因为仍然存在特定于派生类的函数。现在,它只是由编译器生成,而不是由您显式编写。

此功能的工作方式如下:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derived现在有两个构造函数(不包括复制/移动构造函数)。一个接受一个int和一个字符串,另一个接受一个int。


因此,这是假设子类没有自己的构造函数吗?并且继承的构造函数显然不知道要执行的子成员和初始化工作
Suvarna Pattayil 2013年

C ++的定义方式是,类不可能没有自己的构造函数。这就是为什么我不认为“构造函数继承”实际上继承。这只是避免编写乏味的样板的一种方法。
巴特·范·英根·谢瑙

2
我认为混乱的原因是,即使是一个空的构造函数也要进行幕后工作。因此,构造函数实际上包含两部分:内部工作和您编写的部分。由于它们之间没有很好的分隔,因此不会继承构造函数。
Sarien

1
请考虑指出其m()来源,例如,如果类型是,它将如何变化int
Deduplicator

可以using Base::Base隐式执行吗?如果我忘记了派生类上的这一行,而我需要继承所有派生类中的构造函数,将会产生很大的后果
Post Self

7

不清楚“继承父构造函数”是什么意思。使用overriding(覆盖)一词表示您可能正在考虑行为类似于多态虚拟函数的构造函数。我故意不使用“虚拟构造函数”一词,因为这是一种代码模式的通用名称,其中您实际上需要一个对象的现有实例来创建另一个实例。

在“虚拟构造函数”模式之外,多态构造函数几乎没有用,因此很难提出一个具体的场景,在这种情况下可以使用实际的多态构造函数。一个非常人为的例子,它甚至都不是远程有效的C ++

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

在这种情况下,调用的构造函数取决于正在构造或分配的变量的具体类型。它在解析/代码生成期间检测很复杂,并且没有实用程序:您知道要构造的具体类型,并且为派生类编写了特定的构造函数。以下有效的 C ++代码具有完全相同的功能,但略短一些,并且在其作用上更加明确:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


第二个解释或也许是附加的问题是-如果基类构造函数自动出现在所有派生类中,除非被显式隐藏,该怎么办?

如果孩子确实继承了父构造函数,那会出错吗?

您将不得不编写其他代码来隐藏父构造函数,该父构造函数在构造派生类时使用不正确。当派生类以某种无关紧要的方式专门化基类时,可能会发生这种情况。

典型的示例是矩形和正方形(请注意,正方形和矩形通常不能用Liskov替代,因此它不是一个很好的设计,但它突出了这个问题)。

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

如果Square继承了Rectangle的二值构造函数,则可以构造具有不同高度和宽度的正方形...这在逻辑上是错误的,因此您想隐藏该构造函数。


3

为什么不继承构造函数:答案非常简单:基类的构造函数“构建”基类,而继承类的构造函数“构建”继承的类。如果继承的类将继承构造方法,则构造方法将尝试构建基类类型的对象,而您将无法“构建”继承类型类型的对象。

哪种挫败了建立阶级的目的。


3

允许派生类重写基类构造函数的最明显问题是派生类的开发人员现在负责了解如何构造其基类。当派生类不能正确构造基类时会发生什么?

而且,由于您不再可以依靠基类对象的集合彼此兼容,因此Liskov-Substitution原理将不再适用,因为无法保证基类是与其他派生类型正确构建或兼容的。

如果添加的继承级别超过1,则会更加复杂。现在,您的派生类需要知道如何构造链上的所有基类。

如果将新的基类添加到继承层次结构的顶部,会发生什么?您必须更新所有派生类的构造函数。


2

构造函数与其他方法根本不同:

  1. 如果您不编写它们,则会生成它们。
  2. 即使不手动执行,所有基类构造函数都被隐式调用
  3. 您无需显式调用它们,而是通过创建对象来调用它们。

那么为什么不继承它们呢?简单的答案:因为总有一个替代,可以手动生成或覆盖。

为什么每个类都需要一个构造函数?这是一个复杂的问题,我相信答案取决于编译器。存在诸如“琐碎”的构造函数之类的东西,编译器没有为此强制要求将其称为“似乎”。我认为这与您对继承的含义最接近,但是由于上述三个原因,我认为将构造函数与常规方法进行比较并没有真正的用处。:)


1

每个类都需要一个构造函数,甚至是默认构造函数。
C ++将为您创建默认的构造函数,除非您创建专门的构造函数。
如果您的基类使用专门的构造函数,则即使它们相同,也需要在派生类上编写专门的构造函数并将其链接回去。
C ++ 11允许您使用using避免在构造函数上重复代码:

Class A : public B {
using B:B;
....
  1. 一组继承的构造函数由

    • 基类的所有非模板构造函数(省略省略号参数(如果有)之后)(从C ++ 14开始)
    • 对于每个具有默认参数或省略号参数的构造函数,通过删除省略号并从参数末尾省略默认参数形成的所有构造函数签名将一一列出
    • 基类的所有构造函数模板(省略省略号参数(如果有)之后)(从C ++ 14开始)
    • 对于每个带有默认参数或省略号的构造函数模板,通过删除省略号并从参数末尾省略默认参数形成的所有构造函数签名均一一列出
  2. 不是默认构造函数或复制/移动构造函数且其签名与派生类中的用户定义构造函数不匹配的所有继承的构造函数均在派生类中隐式声明。默认参数不继承


0

您可以使用:

MyClass() : Base()

您是否在问为什么必须这样做?

子类可以具有其他属性,这些属性可能需要在构造函数中初始化,或者可以以其他方式初始化基类变量。

您还将如何创建子类型对象?


谢谢。实际上,我试图弄清楚为什么子类不像其他父类方法那样继承构造函数。
Suvarna Pattayil
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.