C++ 11——继承和委派构造函数
继承构造函数
C++ 中自定义类型——类,是C++ 面向对象的基石。类具有可派生性,派生类可以自动获得基类的成员变量和接口。不过基类的非虚函数则无法再被派生类使用了。
如果派生类 要使用基类的构造函数,同城需要在构造函数中显示声明,但是这也带来一个问题:有的时候,我们的基类可能拥有数量众多的不同版本的构造函数,其派生类去构造基类的时候就需要写大量的“透传”构造函数。
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){
}
};
可以看到,这样写很累而且非常的不银杏。事实上,在C++中已经有了一个好用的规则,就是如果派生类要使用基类的成员函数的话,可以通过using
声明来完成。
#include <iostream>
using namespace std;
struct Base
{
void f(double i){
cout<<"base:"<<i<<endl;}
};
struct Derived: Base
{
using Base::f;
void f(int i){
cout<<"derived:"<<i<<endl;}
}
int main()
{
Base b;
b.f(4.5); //base:4.5
Derived d;
d.f(4.5); //derived:4.5
};
这里我们使用using
声明,这样派生类中就拥有了两个版本的重载函数 f 。
在C++ 11中,这个想法被扩展到了构造函数上,子类可以通过使用using
声明来声明继承基类的构造函数。
struct A
{
A(int i){
}
A(double d,int i){
}
A(float f,int i, const char* c){
}
};
struct B: A
{
using A::A; //继承构造函数
};
更为精巧的是,C++ 11标准继承够着函数被设计为跟派生类中各种类默认函数一样,是隐式声明的。这意味着一个继承构造函数不被相关的代码使用,编译器就不会为其产生真正的函数代码。
委派构造函数
与继承构造函数类似,委派构造函数也是C++ 11中对C++的构造函数的一项改进,其目的也是为了减少程序员书写构造函数的时间。
//不使用委派构造函数
class Info
{
public:
Info():type(1),name('a'){
InitRest();}
Info(int i):type(i),name('a'){
InitRest();}
Info(char e):type(1),name(e){
InitRest();}
private:
void InitRest(){
...}
int type;
char name;
};
//成员初始化方式
class Info
{
public:
Info() {
InitRest();}
Info(int i):type(i) {
InitRest();}
Info(char e):name(e) {
InitRest();}
private:
void InitRest(){
...}
int type{
1};
char name{
'a'};
};
我们可以看到,这两种写法使得代码越来越精简,不过每个函数还是需要写一遍InitRest()
。
那么如何去解决这个问题呢?看如下代码:
Info() {
InitRest();}
Info(int i):type(i) {
this->Info();}
Info(char e):name(e) {
this->Info();}
但是这个也有问题:一般的编译器都会阻止this->Info()
的编译。所以,我们可以写一个更有“黑客精神”的版本:
//这个代码有危险嗷
Info() {
InitRest();}
Info(int i):type(i) {
new (this) Info();}
Info(char e):name(e) {
new (this) Info();}
但是有了C++ 11,我们duck不必这样,直接使用委派构造函数的方法即可:
class Info
{
public:
Info() {
InitRest();}
Info(int i):Info() {
type = i;}
Info(char e):Info() {
name = e}
private:
void InitRest(){
...}
int type{
1};
char name{
'a'};
}
我们可以看到,这里我一改常态,在函数体内给type
和name
两个变量赋值。这是因为,在C++ 11中构造函数不能同时“委派”和使用初始化列表,所以如果委派构造函数要给变量赋初值,初始化代码必须放在函数体中。
但是要注意一个问题:在构造函数比较多的时候,我们可能不止有一个委派构造函数,而一些目标构造函数很可能也是委派构造函数,这样一来,我们就可能在委派构造函数中形成链状的委派构造关系:
class Info
{
public:
Info():Info(1,'a') {
}
Info(int i):Info(i,'a') {
}
Info(char e):Info(1,e) {
}
private:
Info(int i,char e):type(i),name(e) {
...}
int type;
char name;
}
但是,这种情况往往又容易产生委托环:
struct Rule2
{
int i,c;
Rule2():Rule2(2){
}
Rule2(int i):Rule2('c'){
}
Rule2(char c):Rule2(2){
}
}
这里Rule2(int i),Rule2(char c)
两个构造函数相互委托,形成环状,会造成编译错误。
参考文献
[1] IBM XL编译器中国开发团队.深入理解C++11.机械工业出版社.2013.06.