委托构造和继承构造函数


配套视频课程已更新完毕,大家可通过以下两种方式观看视频讲解:

关注公众号:爱编程的大丙,或者进入大丙课堂学习。


1. 委托构造函数

委托构造函数允许使用同一个类中的一个构造函数调用其它的构造函数,从而简化相关变量的初始化。下面举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
using namespace std;

class Test
{
public:
Test() {};
Test(int max)
{
this->m_max = max > 0 ? max : 100;
}

Test(int max, int min)
{
this->m_max = max > 0 ? max : 100; // 冗余代码
this->m_min = min > 0 && min < max ? min : 1;
}

Test(int max, int min, int mid)
{
this->m_max = max > 0 ? max : 100; // 冗余代码
this->m_min = min > 0 && min < max ? min : 1; // 冗余代码
this->m_middle = mid < max && mid > min ? mid : 50;
}

int m_min;
int m_max;
int m_middle;
};

int main()
{
Test t(90, 30, 60);
cout << "min: " << t.m_min << ", middle: "
<< t.m_middle << ", max: " << t.m_max << endl;
return 0;
}

在上面的程序中有三个构造函数,但是这三个函数中都有重复的代码,在C++11之前构造函数是不能调用构造函数的,加入了委托构造之后,我们就可以轻松地完成代码的优化了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
using namespace std;

class Test
{
public:
Test() {};
Test(int max)
{
this->m_max = max > 0 ? max : 100;
}

Test(int max, int min):Test(max)
{
this->m_min = min > 0 && min < max ? min : 1;
}

Test(int max, int min, int mid):Test(max, min)
{
this->m_middle = mid < max && mid > min ? mid : 50;
}

int m_min;
int m_max;
int m_middle;
};

int main()
{
Test t(90, 30, 60);
cout << "min: " << t.m_min << ", middle: "
<< t.m_middle << ", max: " << t.m_max << endl;
return 0;
}

在修改之后的代码中可以看到,重复的代码全部没有了,并且在一个构造函数中调用了其他的构造函数用于相关数据的初始化,相当于是一个链式调用。在使用委托构造函数的时候还需要注意一些几个问题:

  • 这种链式的构造函数调用不能形成一个闭环(死循环),否则会在运行期抛异常。

  • 如果要进行多层构造函数的链式调用,建议将构造函数的调用的写在初始列表中而不是函数体内部,否则编译器会提示形参的重复定义。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Test(int max)
    {
    this->m_max = max > 0 ? max : 100;
    }

    Test(int max, int min)
    {
    Test(max); // error, 此处编译器会报错, 提示形参max被重复定义
    this->m_min = min > 0 && min < max ? min : 1;
    }
  • 在初始化列表中调用了代理构造函数初始化某个类成员变量之后,就不能在初始化列表中再次初始化这个变量了。

    1
    2
    3
    4
    5
    // 错误, 使用了委托构造函数就不能再次m_max初始化了
    Test(int max, int min) : Test(max), m_max(max)
    {
    this->m_min = min > 0 && min < max ? min : 1;
    }

2. 继承构造函数

C++11中提供的继承构造函数可以让派生类直接使用基类的构造函数,而无需自己再写构造函数,尤其是在基类有很多构造函数的情况下,可以极大地简化派生类构造函数的编写。先来看没有继承构造函数之前的处理方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
Base(int i) :m_i(i) {}
Base(int i, double j) :m_i(i), m_j(j) {}
Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}

int m_i;
double m_j;
string m_k;
};

class Child : public Base
{
public:
Child(int i) :Base(i) {}
Child(int i, double j) :Base(i, j) {}
Child(int i, double j, string k) :Base(i, j, k) {}
};

int main()
{
Child c(520, 13.14, "i love you");
cout << "int: " << c.m_i << ", double: "
<< c.m_j << ", string: " << c.m_k << endl;
return 0;
}

通过测试代码可以看出,在子类中初始化从基类继承的类成员,需要在子类中重新定义和基类一致的构造函数,这是非常繁琐的,C++11中通过添加继承构造函数这个新特性完美的解决了这个问题,使得代码更加精简。

继承构造函数的使用方法是这样的:通过使用using 类名::构造函数名(其实类名和构造函数名是一样的)来声明使用基类的构造函数,这样子类中就可以不定义相同的构造函数了,直接使用基类的构造函数来构造派生类对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
Base(int i) :m_i(i) {}
Base(int i, double j) :m_i(i), m_j(j) {}
Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}

int m_i;
double m_j;
string m_k;
};

class Child : public Base
{
public:
using Base::Base;
};

int main()
{
Child c1(520, 13.14);
cout << "int: " << c1.m_i << ", double: " << c1.m_j << endl;
Child c2(520, 13.14, "i love you");
cout << "int: " << c2.m_i << ", double: "
<< c2.m_j << ", string: " << c2.m_k << endl;
return 0;
}

在修改之后的子类中,没有添加任何构造函数,而是添加了using Base::Base;这样就可以在子类中直接继承父类的所有的构造函数,通过他们去构造子类对象了。

另外如果在子类中隐藏了父类中的同名函数,也可以通过using的方式在子类中使用基类中的这些父类函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
Base(int i) :m_i(i) {}
Base(int i, double j) :m_i(i), m_j(j) {}
Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}

void func(int i)
{
cout << "base class: i = " << i << endl;
}

void func(int i, string str)
{
cout << "base class: i = " << i << ", str = " << str << endl;
}

int m_i;
double m_j;
string m_k;
};

class Child : public Base
{
public:
using Base::Base;
using Base::func;
void func()
{
cout << "child class: i'am luffy!!!" << endl;
}
};

int main()
{
Child c(250);
c.func();
c.func(19);
c.func(19, "luffy");
return 0;
}

上述示例代码输出的结果为:

1
2
3
child class: i'am luffy!!!
base class: i = 19
base class: i = 19, str = luffy

子类中的func()函数隐藏了基类中的两个func()因此默认情况下通过子类对象只能调用无参的func(),在上面的子类代码中添加了using Base::func;之后,就可以通过子类对象直接调用父类中被隐藏的带参func()函数了。