本文参照于狄泰软件学院,唐佐林老师的——《C++深度剖析教程》
我们先来回忆一下,构造函数有哪些特点吧:
1. 类的构造函数用于对象的初始化
2. 构造函数与类同名并且没有返回值
3. 构造函数在对象定义时自动被调用。
问题:
1. 构造函数既然是用于初始化的,那么我们如何判断构造函数是否初始化成功了呢?
2. 构造函数没有返回值,那么我们在构造函数中调用return语句会发生什么?
3. 构造函数执行结束是否就意味着对象构造成功了呢?
示例代码:异常的构造函数
#include <iostream>
usingnamespace std;
class Test
{
private:
int mi;
int mj;
public:
Test(int i,int j)
{
mi = i;
return;
mj = j;
}
int getI()
{
return mi;
}
int getJ()
{
return mj;
}
};
int main()
{
Test t(1,2);
cout <<"mi = "<< t.getI()<< endl;
cout <<"mj = "<< t.getJ()<< endl;
return0;
}
输出结果:
mi = 1
mj = 21053418646
分析:
1. 在构造函数中,在初始化mj前,我们调用return语句使得构造函数强制退出执行了。导致mj初始化失败。但是对象依然成功生成了,编译器也没有报错。但是我们运行时发现:
mj的值为随机值了。说明构造函数并没有成功初始化mj就已经结束了。
构造函数的机制(不保证初始化成功)
- 构造函数只提供自动初始化成员变量的机会
- 构造函数不保证初始化逻辑一定成功
- 执行return语句后构造函数立即结束。
- 构造函数能决定的只是对象的初始状态,而不是对象的诞生!!
构造函数调用失败并不会影响对象的诞生!!
工程中将这些构造函数不能按照预期完成而得到的对象称为半成品对象,而这些半成品对象在C++中却是合法的,也是Bug的重要来源!
问题:为什么我们要考虑构造函数失败的问题,我们不在构造函数调用return语句不就行了么?
实际上,我们在构造函数中往往会向堆空间申请资源,或者打开一个文件等等系统资源申请的操作,而这时候这些资源申请有可能会失败。但是虽然申请失败了,对象仍然会生成。这样我们使用这个对象时就会出错。
问题:我们该如何判断构造函数是否执行成功了呢?
因为构造函数没有返回值,所以并不能使用return返回一个值来判断是否执行成功。
解决方案:
1. 使用成员变量来判断构造函数是否执行成功
2. 当初始化失败时,抛出异常
使用成员变量来判断够着函数是否执行成功
示例代码:解决方案一
#include <stdio.h>
class Test
{
int mi;
int mj;
bool mStatus;
public:
Test(int i, int j) : mStatus(false)
{
mi = i;
return;
mj = j;
mStatus = true;
}
int getI()
{
return mi;
}
int getJ()
{
return mj;
}
int status()
{
return mStatus;
}
};
int main()
{
Test t1(1, 2);
if( t1.status() )
{
printf("t1.mi = %d\n", t1.getI());
printf("t1.mj = %d\n", t1.getJ());
}
return 0;
}
当初始化失败时,抛出异常(不可行)
示例代码:解决方案二
#include <iostream>
using namespace std;
class Test
{
int mi;
int mj;
public:
Test(int i, int j)
{
mi = i;
if( mi == 1)
throw 0;
mj = j;
}
int getI()
{
return mi;
}
int getJ()
{
return mj;
}
~Test()
{
cout << "~Test()" << endl;
}
};
int main()
{
Test t1(2,3);
try
{
Test* t = new Test(2,2);
cout << "mi = " << t->getI() << endl;
}
catch(...)
{
//delete t;
cout << "the construction be fault..." << endl;
}
cout << "Running end..." << endl;
return 0;
}
分析:
1. 方案一:我们需要另外定义一个成员变量用于判断构造函数是否执行成功。当我们每创建一个对象时,就需要判断这个成员变量是否为true。只有构造函数执行成功了,我们再使用这个对象。
2. 方案二:当遇到构造函数资源申请不成功时,我们可以抛出一个异常。但是构造函数抛出的异常有几个缺点:
a) 构造函数抛出异常后,对象的析构函数不会被调用
b) try…catch属于两个作用域,无法在catch中释放对象资源
相对于方案一,方案二的方法显得更加麻烦。
问题:还有没有更好的解决方案?
可以定义一个成员函数专门用于申请资源,然而成员函数却可以有返回值,这样就能解决部分问题了。详细可以参考《二阶构造模式》。