从 C 向 C++ 进阶系列导航
1. 构造函数的缺陷
构造函数存在以下问题:
- 构造函数只提供自动初始化成员变量的机会,不能保证初始化逻辑一定成功。
- 构造函数中申请系统资源可能失败,失败后构造出一个半成品对象。
- 执行 return 语句后构造函数立即结束。
构造函数创建的对象可能是半成品对象,而半成品对象是合法的,但是却是十分危险的,容易造成难以定位的段错误等程序异常。因此,实际工程开发过程中对于涉及到申请系统资源的构造函数均使用二阶构造模式来杜绝半成品对象的产生。
2. 二阶构造模式的思想
虽然构造函数无返回值,无法通过返回值来判断是否构造成功,但我们可以增加一个表示构造成功与否的变量,通过判断该成员变量可以得知是否构造完成。
- 示例:
class Test
{
private:
bool m_obj_stats;
char* m_p;
public:
Test() : m_obj_stats(ture)
{
m_p = (char*)malloc(1024);
if (m_p == NULL)
{
m_obj_stats = false;
}
}
bool GetObjState()
{
return m_obj_stats;
}
}
以上当 malloc 申请内存失败时,会把 obj_stats 赋值为 false,可通过成员函数 GetObjState() 得知对象是否构造完成。这就是二阶构造模式的思想,当然,具体的形态肯定不是如此,因为这样仍然构造了一个半成品对象,需要人为检查对象是否完整,这是危险的行为。
3. 二阶构造模式的设计
二阶构造模式,即存在两次构造过程,第一次为与系统资源无关的初始化,第二次为与系统资源有关的初始化。
其流程如下:
二阶构造模式实际利用了 static 成员函数的特性,使得在程序中不通过对象而通过 static 成员函数间接地调用类的构造函数与成员函数。
在 static 成员函数中,先申请堆对象的方法构造类对象,在构造函数中完成与系统资源无关的初始化操作,此为一阶构造;然后调用成员函数完成与系统资源有关的初始化操作,同时通过返回值判定是否初始化完成,如果未完成则删除申请的堆对象即可,此为二阶构造。
在主程序中可以通过判定 static 成员函数的返回值来得知是否构造对象成功,假设构造成功,则该对象绝对为完整的类对象。
- 示例:
class Test
{
private:
int m_num;
char* m_p;
Test(int num)
{
this->m_num = num;
}
bool construct()
{
this->m_p = (char *)malloc(this->m_num);
if (this->m_p == NULL)
{
return false;
}
return true;
}
public:
static Test* Array(int len);
};
Test* Test::Array(int len)
{
Test* p = new Test(len);
if (!(p->construct()))
{
delete p;
return NULL;
}
return p;
}
int main()
{
Test* obj = Test::Array(10);
if (obj == NULL)
{
perror("construct obj error!");
return -1;
}
cout << "construct obj sucess!" << endl;
}