C++11 标准引入了继承构造函数(Inheriting Constructors),这是对 C++ 类继承机制的一个重要补充。
在这之前,派生类不能直接使用基类的构造函数来初始化其对象,这导致了,在某些情况下需要在派生类中重复基类构造函数的代码,增加了代码的冗余。继承构造函数特性的引入,旨在减少这种冗余,提高代码的复用性。
提出背景
在 C++11 之前,如果基类有多个构造函数,派生类需要为自己需要用到的每一个基类构造函数编写对应的构造函数,即使这些派生类构造函数只是简单地将参数传递给基类构造函数。
这不仅增加了代码量,也提高了维护成本,因为任何基类构造函数的修改,都需要在所有派生类中进行相应的更新。
继承构造函数允许派生类继承基类的所有构造函数,从而无需在派生类中显式定义这些构造函数。这样做可以显著减少代码重复,使得派生类的编写更加简洁明了。
此外,这还意味着基类构造函数的任何修改都会自动反映在派生类中,无需手动更新派生类代码,从而降低了维护成本。
基本用法
假设有一个基类 Base,它有多个构造函数:
class Base {
public:
Base() {
// 默认构造函数的实现
}
Base(int value) {
// 带参数的构造函数实现
}
};
在 C++11 之前,如果想在派生类Derived中使用这些构造函数,需要显式定义它们:
class Derived : public Base {
public:
Derived() : Base() {
// 派生类使用基类的默认构造函数
}
Derived(int value) : Base(value) {
// 派生类使用基类的带参数构造函数
}
};
而使用C++11的继承构造函数特性,可以简化为:
class Derived : public Base {
using Base::Base; // 继承基类的所有构造函数
};
在这个示例中,通过在派生类中使用 using Base::Base;
语句,Derived 类自动继承了 Base 类的所有构造函数。不需要在 Derived 类中显式定义这些构造函数,就可以像使用 Base 类的构造函数那样使用它们来初始化 Derived 类的对象。
这里使用了 using
关键字,通常情况下,using
声明语句只是令某个名字在当前作用域内可见。**而当作用于构造函数时,using
声明语句将令编译器产生代码。**对于基类的每个构造函数,编译器都生成一个与之对应的派生类构造函数。换句话说,对于基类的每个构造函数,编译器都在派生类中生成一个形参列表完全相同的构造函数。
这些编译器生成的构造函数形如:
Derived(parms) : Base(args) { }
其中,Derived 是派生类的名字,Base 是基类的名字,parms 是构造函数的形参列表,args 将派生类构造函数的形参传递给基类的构造函数。
在我们的 Derived 类示例中,继承的构造函数等价于:
Derived() : Base() { }
Derived(int value) : Base(value) { }
如果派生类含有自己的数据成员,则这些成员将被默认初始化。
继承构造函数与多重继承
如果从多个基类中继承了相同的构造函数(即形参列表完全相同),则程序将产生错误:
struct Base1{
Base1() = default;
Base1(const std::string&);
Base1(std::shared ptr<int>);
};
struct Base2 {
Base2() = default;
Base2(const std::string&);
Base2(int);
};
// 错误: D1 试图从两个基类中都继承D1::D1(const string&)
struct D1 : public Base1, public Base2
{
using Base1::Base1; //从 Base1 继承构造函数
using Base2::Base2; //从 Base2 继承构造函数
};
如果一个类从它的多个基类中继承了相同的构造函数,则这个类必须为该构造函数定义它自己的版本:
struct D2:public Base1, public Base2 {
using Base1::Basel; //从 Base1 继承构造函数
using Base2::Base2; //从 Base2 继承构造函数
// D2 必须自定义一个接受 string 的构造函数
D2(const string &s) : Base1(s), Base2(s) { }
D2()= default; // 一旦 D2 定义了它自己的构造函数,则必须出现
};
继承构造函数的特点
1、和普通成员的 using 声明不一样,一个构造函数的 using 声明不会改变该构造函数的访问级别。
例如,不管 using 声明出现在哪儿,基类的私有构造函数在派生类中还是一个私有构造函数;受保护的构造函数和公有构造函数也是同样的规则。
而且,一个 using 声明语句不能指定 explicit 或 constexpr。如果基类的构造函数是 explicit 或者 constexpr,则继承的构造函数也拥有相同的属性。
2、当一个基类构造函数含有默认实参时,这些实参并不会被继承。相反,派生类将获得多个继承的构造函数,其中每个构造函数分别省略掉一个含有默认实参的形参。
例如,如果基类有一个接受两个形参的构造函数,其中第二个形参含有默认实参,则派生类将获得两个构造函数:
-
一个构造函数接受两个形参(没有默认实参);
-
另一个构造函数只接受一个形参,它对应于基类中最左侧的没有默认值的那个形参。
3、如果基类含有几个构造函数,则除了两个例外情况,大多数时候派生类会继承所有这些构造函数。
第一个例外是,派生类可以继承一部分构造函数,而为其他构造函数定义自己的版本。
如果派生类定义的构造函数,与基类的构造函数具有相同的参数列表,则该构造函数将不会被继承。定义在派生类中的构造函数将替换继承而来的构造函数。
第二个例外是默认、拷贝和移动构造函数不会被继承。这些构造函数按照正常规则被合成。继承的构造函数不会被作为用户定义的构造函数来使用,因此,如果一个类只含有继承的构造函数,则它也将拥有一个合成的默认构造函数。
总结
C++11 中引入的继承构造函数特性极大地简化了派生类的编写,通过允许派生类自动继承基类的构造函数,减少了代码的重复和维护成本。
这提高了代码的复用性和可维护性,使得基于类的继承更加灵活和强大。继承构造函数强化了C++作为一种面向对象编程语言的能力,使得开发者可以更加专注于设计的逻辑,而不是重复的代码实现。