委托构造和继承构造

委托构造函数

委托构造函数允许使用同一个类中的一个构造函数调用其它的构造函数,简化相关变量的初始化。示例代码如下:

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
#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
35
36
#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)如果进行多层构造函数的链式调用,建议将构造函数的调用的写在初始列表中而不是函数体内部,否则编译器会提示形参的重复定义。

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;
}

(3)在初始化列表中调用了委托构造函数初始化某个类成员变量之后,不能在初始化列表中再次初始化这个变量。

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

继承构造函数

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
#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
32
#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
46
#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..." << endl;
}
};

int main()
{
Child c(250);
c.func();
c.func(19);
c.func(19, "carry");

return 0;
}

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

参考资料

https://subingwen.cn/cpp/construct/


委托构造和继承构造
https://lcf163.github.io/2021/09/16/委托构造和继承构造/
作者
乘风的小站
发布于
2021年9月16日
许可协议