C++11 继承构造函数
【1】为什么需要继承构造函数?
首先,看看如下代码片段:
1 struct A 2 { 3 A(int i) 4 {} 5 }; 6 7 struct B : A 8 { 9 B(int i) : A(i) 10 {} 11 };
在C++中非常常见:B派生于A,B在构造函数中调用A的构造函数,从而完成构造函数的“传递”。
有时候,基类可能拥有数量众多的不同版本的构造函数(这样的情况并不少见)。
那么,倘若基类中有大量的构造函数,而派生类却只有一些成员函数时,那么对于派生类而言,其构造就等同于构造基类。
为了遵从语法规则,就需要写很多的“透传”的构造函数。如下面这个例子:
struct A { A(int i) {} A(double d, int i) {} A(float f, int i, const char* c) {} // ... }; struct B : A { B(int i) : A(i) {} B(double d, int i) : A(d, i) {} B(float f, int i, const char* c) : A(f, i, c) {} // ... virtual void ExtraInterface() {} };
在构造B的时候想要拥有A这样多的构造方法的话,就必须一—“透传”各个接口。这无疑是相当不方便的。
而引入继承构造函数的机制就是为了解决这种麻烦的。
【2】using声明
我们知道C++中已经有一个好用的规则:
如果派生类要使用基类的成员函数(被隐藏)的话,可以通过using声明(using-declaration)来完成。请看如下示例:
(1)不声明基类成员函数:
基类成员函数被隐藏(关于隐藏可以参考《重载、覆盖、隐藏》),在派生类中不做声明:
1 #include <iostream> 2 using namespace std; 3 4 struct Base 5 { 6 void f(double i) 7 { 8 cout << "Base:" << i << endl; 9 } 10 }; 11 12 struct Derived : Base 13 { 14 // using Base::f; // 声明基类Base的f函数 15 void f(int i) 16 { 17 cout << "Derived:" << i << endl; 18 } 19 }; 20 21 int main() 22 { 23 Base b; 24 b. f(4.5); 25 Derived d; 26 d. f(4.5); 27 } 28 29 /* 30 Base:4.5 31 Derived:4 32 */
编译警告:warning C4244 : “参数”: 从“double”转换到“int”,可能丢失数据
再结合运行结果分析,说明执行的是派生类Derived中参数为int类型的函数f。
(2)声明基类成员函数:
1 #include <iostream> 2 using namespace std; 3 4 struct Base 5 { 6 void f(double i) 7 { 8 cout << "Base:" << i << endl; 9 } 10 }; 11 12 struct Derived : Base 13 { 14 using Base::f; // 声明继承基类Base的f函数 15 void f(int i) 16 { 17 cout << "Derived:" << i << endl; 18 } 19 }; 20 21 int main() 22 { 23 Base b; 24 b. f(4.5); 25 Derived d; 26 d. f(4.5); 27 } 28 29 /* 30 Base:4.5 31 Base:4.5 32 */
编译无警告,再结合运行结果分析:
说明执行的是基类Base中参数为double类型的函数f。
同时,可以看到派生类Derived中其实有两个f函数的版本。
经过以上的分析,在C++11中,这个特性被扩展到了构造函数上。
即派生类可以通过使用using声明来继承基类的构造函数。
【3】应用继承构造函数
如上示例1,应用继承构造函数,改造如下:
1 #include <iostream> 2 using namespace std; 3 4 struct A 5 { 6 A(int i = 10) : m_a(i) 7 {} 8 9 int m_a; 10 }; 11 12 struct B : A 13 { 14 using A::A; // 继承构造函数 15 16 int m_b{ 100 }; 17 }; 18 19 int main() 20 { 21 B b; 22 cout << b.m_a << endl; // 10 23 cout << b.m_b << endl; // 100 24 25 B bb(200); 26 cout << bb.m_a << endl; // 200 27 cout << bb.m_b << endl; // 100 28 }
通过using A::A的声明,把基类中的构造函数全部继承到派生类B中。
应用注意项:
(1)C++11标准更精巧的是,继承构造函数被设计为跟派生类中的各种类默认函数(默认构造、析构、拷贝构造等)一样,是隐式声明的。
这意味着如果一个继承构造函数不被相关代码使用,编译器不会为其产生真正的函数代码。
这无疑比“透传”方案总是生成派生类的各种构造函数更加节省目标代码空间。
(2)继承构造函数只会初始化基类中成员变量,对于派生类中的成员变量,则无能为力。
不过配合C++11中类成员的初始化表达式,为派生类成员变量设定一个默认值还是没有问题的。
比如此例中的m_b成员变量,设定默认值为100。
(3)基类构造函数的参数可能会有默认值。
对于继承构造函数来讲,基类构造函数参数的默认值是不会被继承的。
比如此例中的bb对象,利用实参200进行构造对象,结果m_a的值为200,而不是默认的10。
(4)基类构造函数有默认值会导致基类产生多个构造函数的版本,这些版本都会被派生类继承。
比如此例中,事实上,构建对象b,使用的均是默认构造函数;构建对象bb,使用的均是带一个参数的构造函数版本。
(5)有的时候,还会遇到继承构造函数“冲突”的情况。这通常发生在派生类拥有多个基类的时候。
多个基类中的部分构造函数可能导致派生类中的继承构造函数的函数名、参数(有的时候,我们也称其为函数签名)都相同,
那么继承类中的冲突的继承构造函数将导致不合法的派生类代码,如下示例:
1 struct A { A(int) {} }; 2 3 struct B { B(int) {} }; 4 5 struct C : A, B 6 { 7 using A::A; 8 using B::B; 9 };
这种情况下,可以通过显式定义继承类的冲突的构造函数,阻止隐式生成相应的继承构造函数来解决冲突:
1 struct A { A(int) {} }; 2 3 struct B { B(int) {} }; 4 5 struct C : A, B 6 { 7 C(int c) : A(c), B(c) 8 {} 9 };
(6)如果基类的构造函数被声明为私有成员函数,或者派生类是从基类中虚继承的,那么就不能够在派生类中声明继承构造函数。
(7)如果基类的构造函数没有默认构造函数,那么一旦使用了继承构造函数,编译器也不会再为派生类生成默认构造函数。
如下示例不能够通过编译:
1 struct A 2 { 3 A(int) {} 4 }; 5 6 struct B : A 7 { 8 using A::A; 9 }; 10 11 int main() 12 { 13 B objB; // C2280 “B::B(void)”: 尝试引用已删除的函数 14 }
个人认为,解决这种问题有两种方式,代码如下:
1 #if 0 // 方式一:基类定义默认构造函数,派生类自然也就有了 2 struct A { A(int a = 10) {} }; 3 struct B : A 4 { 5 using A::A; 6 }; 7 #else // 方式二:派生类自定义默认构造函数 8 struct A { A(int) {} }; 9 struct B : A 10 { 11 using A::A; 12 B(int c = 20) : A(c) 13 {} 14 }; 15 #endif 16 17 int main() 18 { 19 B objB; 20 }
如上所述。
good good study, day day up.
顺序 选择 循环 总结