C++11 标准新特性:委派构造函数

WeiboGoogle+用电子邮件发送本页面

Comments
 
1

本文首先介绍了在委派构造函数提出之前类成员构造所面临的问题,再结合实例介绍了委派构造函数的用法,并说明了使用委派构造函数特性时需要注意的语言点。在本文最后还介绍了 IBM XL C/C++编译器用来控制该特性的编译选项。

特性背景

在 C++98 中,如果一个类有多个构造函数且要实现类成员构造,这些构造函数通常要包含基本相同的类成员构造代码。在最坏的情况下,相同的类成员构造语句被拷贝粘贴在每一个构造函数中,请参考下面的例子:

清单 1.程序源代码 (ctorini.cpp)
1
2
3
4
5
6
7
8
9
10
11
class A{
public:
// 构造函数 A(), A(int i)和 A(int i, int j)有相同的函数体
A(): num1(0), num2(0) {average=(num1+num2)/2;}
A(int i): num1(i), num2(0) {average=(num1+num2)/2;}
A(int i, int j): num1(i), num2(j) {average=(num1+num2)/2;}
private:
int num1;
int num2;
int average;
};

上例中的三个构造函数有完全相同的函数体。重复的代码会增大维护的工作量和难度。如果程序员想增加更多的类成员构造语句或者修改已有类成员的类型,他需要在三个构造函数中做三次完全相同的改动。为了避免代码重复,一些程序员将公有的类成员构造代码移到一个类成员函数里,意图让构造函数通过调用该成员函数完成类成员构造。清单 1 的程序根据这个思路,可以修改如下:

清单 2.程序源代码 (memfuncini.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
class A{
public:
// 构造函数 A(), A(int i)和 A(int i, int j)意图通过调用成员函数 int()来完成类成员构造
A(): num1(0), num2(0) {init();}
A(int i): num1(i), num2(0) {init();}
A(int i, int j): num1(i), num2(j) {init();}
private:
int num1;
int num2;
int average;
void init(){ average=(num1+num2)/2;};
};

清单 2 的程序消除了代码重复的问题,但又引入了以下的新问题:

  • 其它的成员函数可能会不小心调用到 init()函数,这样会造成不可预期的结果。
  • 当编译器处理成员函数调用的时候,全部的类成员其实都已经了完成构造。也就是说,想在类成员函数里实现类成员构造是行不通的。

委派构造函数的提出和基本用法

基于以上类成员构造所面临的问题,C++11 标准提出了委派构造函数新特性。利用这个特性,程序员可以将公有的类成员构造代码集中在某一个构造函数里,这个函数被称为目标构造函数。其他构造函数通过调用目标构造函数来实现类成员构造,这些构造函数被称为委派构造函数。在该新特性提出之前,构造函数是不能显式被调用的,委派构造函数打破了这一限制。让我们用下面例子来说明该新特性是如何使用的:

清单 3.程序源代码 (delegatingctor1.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A{
public:
// A(int i)为 A()的委派构造函数
A(): A(0){}
// A(int i, int j)为 A(int i)的委派构造函数
A(int i): A(i, 0){}
// 委派构造链为 A()->A(int i)->A(int i, int j)
A(int i, int j) {
num1=i;
num2=j;
average=(num1+num2)/2;
}
private:
int num1;
int num2;
int average;
};

可以看到,在构造函数 A()的初始化列表里,程序调用了 A(0), 这就是委派构造函数的语法。 我们称 A(int i)为 A()的目标构造函数,而 A()为 A(int i)的委派构造函数。同理,A(int i, int j)为 A(int i)的目标构造函数,而 A(int i) 为 A(int i, int j)的委派构造函数。在利用了委派构造函数后,整个程序变得更加的清楚和简洁。目标构造函数和委派构造函数跟其他普通的构造函数一样有相同的接口和语法,它们并没有特殊的处理和标签。从这个例子还可以看到,一个委派构造函数可以是另一个委派构造函数的目标构造函数,委派构造函数和目标构造函数是相对而言的。目标构造函数是通过重载和类参数推导准则而选定的。

在委派过程中,当目标构造函数函数执行完毕后,委派构造函数继续执行它自己函数体内的其他语句。请参考以下的例子:

清单 4.程序源代码 (delegatingctor2.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
class A{
public:
 A(): A(0){ cout << "In A()" << endl;}
A(int i): A(i, 0){cout << "In A(int i)" << endl;}
A(int i, int j){
num1=i;
num2=j;
average=(num1+num2)/2;
cout << "In A(int i, int j)" << endl;
private:
int num1;
int num2;
int average;
};
int main(){
A a;
return 0;
}

该例子的输出为:

1
2
3
In A(int i, int j)
In A(int i)
In A()

委派构造函数的异常处理

当目标构造函数抛出异常时,该异常会被委派构造函数中的 try 模块抓取到。并且在这种情况下,委派构造函数自己函数体内的代码就不会被执行了。

在下面的例子中,构造函数 A(int i, int j)抛出一个异常,该异常依次被委派构造函数 A(int i)和 A()抓取到,且 A(int i)和 A()的函数体没有被执行。

清单 5.程序源代码 (exception1.cpp)
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
47
48
49
50
#include <iostream>
using namespace std;
class A{
public:
A();
A(int i);
A(int i, int j);
private:
int num1;
int num2;
int average;
};
A:: A()try: A(0) {
// A()函数体不会被执行到
cout << "A() body"<< endl;
}
catch(...) {
cout << "A() catch"<< endl;
}
A::A(int i) try : A(i, 0){
// A(int i)函数体不会被执行到
cout << "A(int i) body"<< endl;
}
catch(...) {
cout << "A(int i) catch"<< endl;
}
A::A(int i, int j) try {
num1=i;
num2=j;
average=(num1+num2)/2;
cout << "A(int i, int j) body"<< endl; 
// 抛出异常
 throw 1;
}
catch(...) {
cout << "A(int i, int j) catch"<< endl;
}
int main(){
try{
A a;
cout << "main body"<< endl;
}
catch(...){
cout << "main catch"<< endl;
}
return 0;
}

该例的输出为:

1
2
3
4
5
A(int i, int j) body
A(int i, int j) catch
A(int i) catch
A() catch
main catch

当委派构造函数抛出异常时,系统会自动调用目标构造函数内已经构造完成的对象的析构函数。在下面的例子中,目标构造函数 A(int i, int j)完成了对象 a 的构造。它的委派构造函数 A(int i)在执行时抛出了一个异常,此时对象 a 马上被析构,且 A(int i)的委派构造函数 A()的函数体不再被编译器执行,这和上例中所描述的原则是一致的。

清单 6.程序源代码 (exception2.cpp)
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 A{
public:
A();
A(int i);
A(int i, int j);
~A();
private:
int num1;
int num2;
int average;
};
A::A(): A(0){
// A()函数体不会被执行到
cout << "A()body" << endl;
}
A::A(int i) try : A(i, 0){
cout << "A(int i) body"<< endl;
// 抛出异常,对象 a 将被析构
throw 1;
}
catch(...) {
cout << "A(int i) catch"<< endl;
}
A::A(int i, int j){
num1=i;
num2=j;
average=(num1+num2)/2;
cout << "A(int i, int j) body"<< endl;
}
A::~A(){
cout << "~A() body"<< endl;
}
int main(){
A a;
return 0;
}

该例的输出为:

1
2
3
4
A(int i, int j) body
A(int i) body
~A() body
A(int i) catch

委派构造函数和泛型编程

综上所述,委派构造函数可以使程序员规避构造函数里重复的代码。委派构造函数还有一个很实际的应用:它使得构造函数的泛型编程变得更加容易,请参考以下的例子:

清单 7.程序源代码 (generic.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;
template<typename T> class A{
public:
A(int i): A(i, 0){}
A(double d): A(d, 0.0){}
// 函数模板
A(T i, T j) {
num1=i;
num2=j;
average=(num1+num2)/2;
cout << "average=" << average << endl;
}
private:
T num1;
T num2;
T average;
};
int main(){
A<int> a_int(1);
A<double> a_double(1.0);
}

该例的输出为:

1
2
average=0
average=0.5

在这个例子中,目标构造函数为函数模板,它在被委派构造函数调用的时候才被实例化。这样的用法十分方便,程序员不需要再书写不同类型的目标构造函数了。

委派构造函数的使用限制

委派构造函数特性简单好用,非常利于新手的学习和掌握,但是在这个特性的使用中也需要注意以下的限制。

如清单 3 中的程序所示, 一个类中的构造函数可以形成一个委派链。但是程序员应该避免委派环的出现。在下面的例子中,构造函数 A(), A(int i), 和 A(int i, int j)形成了一个委派环,这样的程序是有语法错误的。

清单 8.程序源代码 (delegatingcircle.cpp)
1
2
3
4
5
6
7
8
9
10
11
class A{
public:
//形成了委派构造环 A()->A(int i)->A(int i, int j)->A()
A(): A(0){}
A(int i): A(i, 0){}
 A(int i, int j): A(){}
private:
int num1;
int num2;
int average;
};

委派构造函数不能在初始化列表中包含成员变量的初始化.也就是说,一个构造函数不能在初始化列表中既初始化成员变量,又委派其他构造函数完成构造。在下面的例子中,A()是 A(int i)的委派构造函数,并且在同时又初始化了类成员变量 average。这样的程序是不被允许的。

清单 9.程序源代码 (delegatingini.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A{
public:
//错误,A()不能同时委派和初始化类成员
A(): A(0), average(0){}
A(int i): A(i, 0){}
A(int i, int j) {
num1=i;
num2=j;
average=(num1+num2)/2;
}
private:
int num1;
int num2;
int average;
};

编译选项

IBM XL C/C++编译器已经实现了委派构造函数这个新特性。在默认情况下,该特性是关闭的,如果程序员需要打开该特性,可以设置下面任意一个编译选项:

  • -qlanglvl=[no]delegatingctors

默认值: -qlanglvl=nodelegatingctors

用法: -qlanglvl=delegatingctors 打开委派构造函数特性,

-qlanglvl=nodelegatingctors 关闭委派构造函数特性。

  • -qlanglvl=extended0x

该编译选项控制所有的 C++11 特性,当程序员设置了该编译选项时,所有的 IBM XL C/C++编译器实现的 C++11 特性都会被打开,包括委派构造函数特性。

除了 IBM XL C/C++编译器,其他很多编译器也实现了该特性,比如 clang,GNU C++编译器等。

结束语

本文详细的介绍了 C++11 标准新特性-委派构造函数。利用该特性,可以提高程序的可读性和可维护性,减少程序员书写构造函数的时间。通过委派其他构造函数,多构造函数的类编写将更加容易。但委派构造函数的调用是需要系统开销的,如果可以用带默认参数的构造函数实现和委派构造函数相同的功能,在不影响程序可读性和可维护性的前提下,更推荐程序员使用前者。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384079.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

顺序表实现队列

一. 队列相关概念 队列是只允许在一段进行插入元素, 在另一端进行删除元素的线性表,即只允许对队列进行尾插,头删的操作.队列具有先进先出, 后进后出的特性.          1.初始化 void SeqQueInit(SeqQue* q) {if(q NULL){return;//非法输入}q -> head 0;q -> …

Arbitrage——判断正环Bellman-Ford/SPFA

【题目描述】 Arbitrage is the use of discrepancies in currency exchange rates to transform one unit of a currency into more than one unit of the same currency. For example, suppose that 1 US Dollar buys 0.5 British pound, 1 British pound buys 10.0 French …

链表实现队列

上篇博客是用顺序表实现队列, 现在用双向带头结点带环链表实现对队列的出队列, 入队列, 取队首元素, 以及销毁队列的相关操作 1.初始化链表 void DLinkQueInit(DLinkQue** q) {if(q NULL){return;//非法输入}if(*q NULL){return;//非法输入带头结点的链表至少有一个傀儡结点…

HDU - 1796——容斥原理+二进制枚举

【题目描述】 Now you get a number N, and a M-integers set, you should find out how many integers which are small than N, that they can divided exactly by any integers in the set. For example, N12, and M-integer set is {2,3}, so there is another set {2,3,4,…

数据结构学习(二)——单链表的操作之头插法和尾插法创建链表

http://blog.csdn.net/abclixu123/article/details/8210109 链表也是线性表的一种&#xff0c;与顺序表不同的是&#xff0c;它在内存中不是连续存放的。在C语言中&#xff0c;链表是通过指针相关实现的。而单链表是链表的其中一种&#xff0c;关于单链表就是其节点中有数据域和…

信号的基本概念以及信号的产生

一. 信号产生的场景 1. 用户输入命令, 在shell 启动一个前台进程      2. 当用户按一下 Ctrl C 的时候,从键盘产生一个硬件中断      3. 此时CPU 正在执行这个进程的带代码, 则该进程的执行代码暂停执行, CPU 从用户态切换到内核态处理该硬件中断.      4. 中断…

HDU - 1028——母函数入门

【题目描述】 “Well, it seems the first problem is too easy. I will let you know how foolish you are later.” feng5166 says. “The second problem is, given an positive integer N, we define an equation like this: Na[1]a[2]a[3]…a[m]; a[i]>0,1<m<N;…

信号的阻塞

一. 阻塞信号 1.信号的相关概念     (1) 递达: 实际执行信号的处理动作称为信号的递达     (2) 未决: 信号从产生到递达之间的过程叫做信号的未决     (3) 阻塞: 进程可以选择阻塞某个信号, 被阻塞的信号产生时将保持在未决状态, 直到进程解除该信号的屏蔽, 才…

POJ 1511 Invitation Cards——Dijkstra优先队列优化+反向建图

【题目描述】 In the age of television, not many people attend theater performances. Antique Comedians of Malidinesia are aware of this fact. They want to propagate theater and, most of all, Antique Comedies. They have printed invitation cards with all the…

链栈 尹成

http://blog.csdn.net/itcastcpp/article/details/39271661 今天&#xff0c;我们一起用C写链栈&#xff0c;具体如下。 LinkStack.h具体内容&#xff1a; [cpp] view plaincopy #include "StackNode.h" template<typename Type> class LinkStack{ publ…

信号的捕捉以及SIGCHLD信号

一. 信号的捕捉定义 用户提供一个处理函数, 要求内核在处理信号时必须切换到用户态,执行这个函数, 这种方式就叫做信号的捕捉 二. 图解信号的捕捉过程 1. 由上图可以看出,当处理信号的执行动作时用户自定义的时候,此时就需返回该函数去调用该函数, 这就叫做信号的捕捉. 以前我…

链队 尹成

http://blog.csdn.net/itcastcpp/article/details/39271691 今天&#xff0c;我们一起用C写一个链对&#xff0c;具体如下所示。 LinkQueue.h具体内容如下&#xff1a; [cpp] view plaincopy #include "QueueNode.h" template<typename Type> class LinkQueu…

强连通分量入门——Trajan算法

今天学习了强连通分量。 【参考博客】 如果觉得我讲的有些地方难以理解或者存在问题&#xff08;欢迎留言&#xff09;&#xff0c;可以看一下我借鉴的一些大佬的博客&#xff1a; 传送门1 传送门2 【知识储备】 首先我们需要对几个定义有一些概念&#xff1a; 强连通&#xff…

最小栈的实现

所谓最小栈, 就是当前栈顶元素最小, 我们可以这样做, 每次在入栈之前, 将待入栈元素与栈顶元素相比, 每次现将待入栈的元素先入栈, 再将带入栈的元素和较小的元素入栈, 这样就可以保证每次栈顶元素是最小元素. 在出栈的时候规定每次出栈两个元素,这样就可以保证在出栈之后栈顶元…

用C++实现单链表的创建、逆置和输出 的两种方法

http://blog.csdn.net/lfeng_coding/article/details/47300563 题目描述&#xff1a;在已知单链表头节点的情况下&#xff0c;设计算法逆置单链表并输出 方法一&#xff1a;采用首先将头节点指向空&#xff0c;让其变为尾节点&#xff0c;然后利用中间节点 p、q 将其后的节点一…

两个栈实现一个队列

利用两个栈实现一个队列思路是这样的. 首先这个队列包含两个栈, 然后一个栈用来入队列, 一个栈用来出队列 typedef struct QueBy2Stack {SeqStack input;SeqStack output; }QueBy2Stack; 1. 初始化 void QueBy2StackInit(QueBy2Stack* stack) {if(stack NULL){return;//非法…

HDU 5934:Boom——强连通分量+缩点

【题目描述】 There are N bombs needing exploding.Each bomb has three attributes: exploding radius ri, position (xi,yi) and lighting-cost ci which means you need to pay ci cost making it explode.If a un-lighting bomb is in or on the border the exploding ar…

Linux--线程死锁

http://blog.csdn.net/gebushuaidanhenhuai/article/details/73799824 线程为什会死锁&#xff1f;&#xff1f;“锁”又是什么东西&#xff1f;我们这篇博客主要讲一下为什么要给线程加锁&#xff0c;为什么会出现线程死锁&#xff0c;线程死锁怎么解决。 互斥锁 在我的上篇博…

两个队列实现一个栈

用两个队列实现一个栈的原理是这样的. 规定两个队列, 必须有一个队列是非空, 一个队列是空.每次入栈时必须往非空队列中入, 而每次出栈时, 必须将非空队列里的元素装到空队列中, 直到非空队列中只有一个元素时, 此时就将剩下的这个元素出栈即可. 而取栈顶元素时, 和出栈一样, 先…

POJ-1144 Network——Trajan+割点

【题目描述】 A Telephone Line Company (TLC) is establishing a new telephone cable network. They are connecting several places numbered by integers from 1 to N . No two places have the same number. The lines are bidirectional and always connect together tw…