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 -> …

链表实现队列

上篇博客是用顺序表实现队列, 现在用双向带头结点带环链表实现对队列的出队列, 入队列, 取队首元素, 以及销毁队列的相关操作 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,…

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

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

信号的阻塞

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

信号的捕捉以及SIGCHLD信号

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

最小栈的实现

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

用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;//非法…

Linux--线程死锁

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

两个队列实现一个栈

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

Linux--生产者与消费者

http://blog.csdn.net/gebushuaidanhenhuai/article/details/74011636 基本概念 提到生产者和消费者&#xff0c;我们最有可能想到的是商店卖东西&#xff0c;顾客在货架上(缓冲区&#xff09;买东西。 生产者消费者问题&#xff0c;其实是一个多线程同步问题的经典案例。该问…

进程的挂起以及可重入函数

相关接口     pause 函数用于将进程挂起. 如果信号的处理动作是终止进程, 则进程终止, pause 函数没有返回值; 如果信号的处理动作是忽略, 则进程被挂起, pause函数不返回, 如果信号的处理动作是捕捉, 则调用信号处理动作之后pause 返回 -1.来看一段代码 #include<s…

gdb调试多进程程序

1.gdb下调试多进程程序只需要以下几条命令即可              除此之外还可以查看正在调试的进程 info inferiors, 同时也可以将当前正在调试的进程切换到另外一个进程中让其取运行     2.代码调试演示 #include<stdio.h> #include<stdlib.h> #…

关于memcpy和memmove两函数的区别

http://blog.csdn.net/caowei840701/article/details/8491836 [cpp] view plaincopy <p> 关于memcpy和memmove两个c标准库函数&#xff0c;其功能都是将一块内存区域中的指定大小内容复制到目标内存中&#xff0c;在翻阅c标准库实现的源代码我们发现他们是有区别的。&…

判断字符串出栈合法性

先来看说一下思路 接下来就是写代码了 int StackOrder(SeqStack* stack, char* input, char* output, int size_input, int size_output) {if(stack NULL || input NULL || output NULL){return 0;}int i_input 0;int j_output 0;SeqStackType value;for(; j_output <…

共享栈

1.定义 所谓共享栈就是利用一个数组实现两个栈. 先来看一下共享栈的数据结构 typedef struct SharedStack {int top1;int top2;SeqStackType* data; }SharedStack; 2. 初始化 void SharedStackInit(SharedStack* stack) {if(stack NULL){return;//非法输入}stack -> top…

迷宫求解(递归)

首先来看一下迷宫简易图                                  我们用 0 来表示该位置是墙, 用 1 来表示该位置是路. 所以, 我们在处理迷宫问题的时候可以将其看成一个二维数组即可, 而对应的每一条路我们可以用坐标的形式将其表示, 所以还需要…

数据结构练习——双向链表

http://www.cnblogs.com/-Lei/archive/2012/04/10/2440399.html 复习一下数据结构。。。。说不准下个星期就用上了 只不过写的很简单&#xff0c;没有封装 DoubleLinkList.h #ifndef GUARD_DoubleLinkList_h #define GUARD_DoubleLinkList_h#include <stdio.h>struct Li…

线程的终止分离

1.线程的终止 注意该函数是针对用户级别的, 其中 retal 必须指向一个全局变量, 或者是一个 malloc 分配的, 因为如果是线程的局部变量, 当该线程退出时, 其他线程不能得到这个变量, 因为线程的局部变量各自私有 2. 现成的取消 其中thread是线程的 tid 3.线程的等待与分离 (1)…