C++深度解析 二阶构造模式 --- 半成品对象(26)

C++深度解析 二阶构造模式(26)

 

 

 

二阶构造模式《===等价于===》二阶构造方法

关于构造函数

  • 类的构造函数用于对象的初始化
  • 构造函数与类同名并且没有返回值
  • 构造函数在对象定义时自动被调用

 

 

 

面试问题,并且引入课题:

1、如何判断构造函数的执行结果?答:没有办法判断。

2、在构造函数中执行return语句会发生什么?答:可以存在return语句,执行return语句后,函数立即返回。

3、构造函数执行结束是否意味着对象构造成功?答:并不意味对象构造成功。

示例程序:(异常的构造函数)

#include <stdio.h>

class Test
{
    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 t1(1, 2);
    
    printf("t1.mi = %d\n", t1.getI());
    printf("t1.mj = %d\n", t1.getJ());
    
    return 0;
}

结果如下:

分析:

mj的值变成了随机数。成员变量mj没有被初始化,构造函数体执行到return语句时,直接返回。

创建对象t1,虽然执行了构造函数,但是构造函数内部有问题,没有影响对象t1的诞生。对象的诞生与构造函数的执行结果没有任何关系的。

示例程序:(简单的解决方案)

#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);
    
    // 每次创建对象后,都要判断一下mStatus是什么
    if (t1.status())
    {
        printf("t1.mi = %d\n", t1.getI());
        printf("t1.mj = %d\n", t1.getJ());
    }
    
    return 0;
}

结果如下:

分析:这个解决方案,强行让构造函数有一个返回值,并且手工调用status来得到构造函数的返回值。这样做并不雅观。

 

 

 

从上面的两个程序可以得出下面的结论:

构造函数

  • 只提供自动初始化成员变量的机会
  • 不能保证初始化逻辑一定成功
  • 执行return语句后构造函数立即结束

构造函数能决定的只是对象的初始状态,而不是对象的诞生!!

 

 

 

半成品对象

初始化操作不能按照预期完成而得到的对象

半成品对象是合法的C++对象也是Bug的重要来源

示例程序:(半成品对象的危害)

 

 

 

二阶构造(分两步来构造对象)

二阶构造函数,主要适用于在构造函数申请系统资源的场景。普通构造函数的作用是初始化对象。若在初始化过程中不能按照程序员预期的进行,那么将会得到一个半成品对象。

资源无关的初始化操作

  • 不可能出现异常情况的操作

需要使用系统资源的操作

  • 可能出现异常情况,如:内存申请,访问文件

二阶构造示例一:

class TwoPhaseCons {
private:
	TwoPhaseCons () {	//第一阶段构造函数:C++构造函数,简单的赋值操作
	}
	bool construct () {	//第二阶段构造函数:系统的资源申请,打开文件,打开网络等,可能产生失败的操作
		return true;
	}
public:
	static TwoPhaseCons* NewInstance();	//对象创建函数
};

分析:构造函数TwoPhaseCons()是私有的,构造函数不能给外界调用了,不能通过类名定义对象。通过NewInstance()创建对象。

二阶构造示例二:

//创建函数
TwoPhaseCons* TwoPhaseCons::NewInstance() {
	//在堆空间创建对象
	//执行第一阶段的构造,赋值的操作
	TwoPhaseCons* ret = new TwoPhaseCons();

	//第二阶段:资源申请的操作
	//若第二阶段构造失败,返回NULL
	if ( !(ret && ret->construct()) ) {
		delete ret;
		ret = NULL;
	}
	return ret; 
}

分析:上面的代码中,TwoPhaseCons* ret = new TwoPhaseCons();  , 构造函数TwoPhaseCons()声明为私有的,在静态成员函数的内部静态成员函数 通过指针可以直接访问私有成员。

示例代码:(完整的二阶构造程序)

#include <stdio.h>

class TwoPhaseCons
{
private:
    TwoPhaseCons() 
    { //第一阶段构造函数
    }
    bool construct() 
    { //第二阶段构造函数
        return true;
    }
public:
    static TwoPhaseCons* NewInstance(); //对象创建函数
};

TwoPhaseCons* TwoPhaseCons::NewInstance() 
{
    TwoPhaseCons* ret = new TwoPhaseCons();
    
    //若第二阶段构造失败,返回 NULL
    if ( !(ret && ret->construct()) ) {
        delete ret;
        ret = NULL;
    }
    return ret;
}

int main()
{
    TwoPhaseCons* obj = TwoPhaseCons::NewInstance();
    //TwoPhaseCons* obj = new TwoPhaseCons(); //错误的,因为构造函数TwoPhaseCons()是私有的
    
    printf("obj = %p\n", obj);
    
    return 0;
}

结果如下:

分析:

二阶构造,要么得到一个合法可用的对象,要么返回一个空nil二阶构造杜绝半成品的对象。

用二阶构造的方式设置类的构造函数,就不能让用户利用该类直接生成对象,因为这样操作会使得默认的构造函数被调用,而忽略了二阶构造的construct()函数。用户要生成对象需要使用类中的静态函数NewInstance(),NewInstance()执行new对象操作,此时会调用默认构造函数TwoPhaseCons(),在此函数中实现不可能会出现异常的操作,成为一阶构造。new成功的前提下调用construct()函数,在此函数中实现可能会出现异常的操作,称为二阶构造。在确保一阶构造和二阶构造都执行成功的前提下返回,NewInstance()才返回new的对象ret的地址,反之返回nil。

 

 

 

运用二阶构造,增强数组类

步骤:

1、将构造函数,从public移到private。

2、添加并实现一个函数,用于第二阶段的构造construct()。

3、添加并实现一个对象的创建函数NewInstance()。

IntArray.h

#ifndef _INTARRAY_H_

#define _INTARRAY_H_


class IntArray

{

private:

    int m_length;

    int* m_pointer;
    IntArray(int len);

    IntArray(const IntArray& obj);
    bool construct();

public:
    static IntArray* NewInstance(int length);

    int length();

    bool get(int index, int& value);

    bool set(int index ,int value);

    ~IntArray();

};



#endif

IntArray.cpp

#include "IntArray.h"



IntArray::IntArray(int len)

{   

    m_length = len;

}


bool IntArray::construct()
{
    bool ret = true;
    
    m_pointer = new int[m_length];

    

    if(m_pointer)

    {

        for(int i=0; i<m_length; i++)

        {

            m_pointer[i] = 0;

        }

    }
    else
    {
        ret = false;
    }
    
    return ret;
}

IntArray* IntArray::NewInstance(int length)
{
    IntArray* ret = new IntArray(length);
    
    if( !(ret && ret->construct()))
    {
        delete ret;
        ret = 0;
    }
    
    return ret;
}


int IntArray::length()

{

    return m_length;

}



bool IntArray::get(int index, int& value)

{

    bool ret = (0 <= index) && (index < length());

    

    if( ret )

    {

        value = m_pointer[index];

    }

    

    return ret;

}



bool IntArray::set(int index, int value)

{

    bool ret = (0 <= index) && (index < length());

    

    if( ret )

    {

        m_pointer[index] = value;

    }

    

    return ret;

}



IntArray::~IntArray()

{

    delete[]m_pointer;

}

main.cpp

#include <stdio.h>

#include "IntArray.h"



int main()

{

    IntArray* a = IntArray::NewInstance(5); 
    
    printf("a.length = %d\n", a->length());
       

    a->set(0, 1);

  

    for(int i = 0; i < a->length(); i++)
    {
        int v = 0;
        
        a->get(i, v);
        
        printf("a[%d] = %d\n", i, v);
    }
    
    delete a;
    

    return 0;

}

结果如下:

分析:运用了二阶构造,对象只能在堆空间产生,不能在栈上产生一个对象。

 

 

 

小结:

构造函数只能决定对象的初始化状态

构造函数中初始化操作的失败不影响对象的诞生

初始化不完全的半成品对象是Bug的重要来源

二阶构造,人为的将初始化过程分为两部分

二阶构造能够确保创建的对象都是完整初始化的。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值