C++深入探究二阶构造模式的原理与使用

目录
  • 一、构造函数的回顾
  • 二、半成品对象
  • 三、二阶构造
  • 四、小结

一、构造函数的回顾

关于构造函数

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

问题

  • 如何判断构造函数的执行结果?
  • 在构造函数中执行 return 语句会发生什么?
  • 构造函数执行结束是否意味着对象构造成功?

下面看一个异常的构造函数:

#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;
}

运行结果如下,可以看到,没有输出,遇到 return 构造函数就结束了:

构造函数

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

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

二、半成品对象

半成品对象的概念

  • 初始化操作不能按照预期完成而得到的对象
  • 半成品对象是合法的 C++ 对象,也是 Bug 的重要来源

下面来看一个半成品对象的危害:

IntArray.h:

#ifndef _INTARRAY_H_
#define _INTARRAY_H_
class IntArray
{
private:
    int m_length;
    int* m_pointer;
public:
    IntArray(int len);
    IntArray(const IntArray& obj);
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    ~IntArray();
};
#endif

IntArray.c:

(注意:m_pointer = 0; //假设 m_pointer 为空指针,用来模拟申请内存失败的情况)

#include "IntArray.h"
IntArray::IntArray(int len)
{
    m_pointer = 0;  //假设 m_pointer 为空指针,用来模拟申请内存失败的情况
    if( m_pointer )
    {
        for(int i=0; i<len; i++)
        {
            m_pointer[i] = 0;
        }
    }
    m_length = len;
}
IntArray::IntArray(const IntArray& obj)
{
    m_length = obj.m_length;
    m_pointer = new int[obj.m_length];
    for(int i=0; i<obj.m_length; i++)
    {
        m_pointer[i] = obj.m_pointer[i];
    }
}
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(5);
    printf("a.length = %d\n", a.length());
    a.set(0, 1);
    return 0;
}

输出结果如下:

产生段错误是因为前面令m_pointer = 0; 模拟内存申请不成功。但是在实际工程中,不是每次申请内存都不成功,所以很难重现,堪称最难调试的 bug。

三、二阶构造

工程开发中的构造过程可分为

  • 资源无关的初始化操作
  • 不可能出现异常情况的操作

需要使用系统资源的操作

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

二阶构造示例一

二阶构造示例二

下面初探一下二阶构造函数:

#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();
    printf("obj = %p\n", obj);
    delete obj;
    return 0;
}

运行结果如下,指针的值被打印出来,意味着可以得到一个合法可用的对象,这个对象位于堆空间上:

如果我们就不想用二阶构造,自己申请堆空间,如下:

TwoPhaseCons* obj = new NewInstance();

就会报错,因为构造函数是私有的:

如果第二阶段的构造不成功:

    bool construct() // 第二阶段构造函数
    {
        return false;
    }

输出结果如下,打印结果为空:

所以二阶构造的意义就是要么得到一个合法可用的对象,要么返回空。二阶构造用于杜绝半成品对象。

所以前面写的可能产生半成品对象的代码可以写成:

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.c:

#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.c:

#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 的重要来源
  • 二阶构造人为的将初始化过程分为两部分
  • 二阶构造能够确保创建的对象都是完整初始化的

到此这篇关于C++深入探究二阶构造模式的原理与使用的文章就介绍到这了,更多相关C++二阶构造模式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解C++-二阶构造模式、友元

    首先回顾以前所学的构造函数 类的构造函数用于对象的初始化 构造函数与类同名并且没有返回值 构造函数在定义时被自动调用 由于构造函数没有返回值不能判断执行结果,所以不能保证初始化对象能否成功 比如: class Test{ private: int *p; public: Test(int i) { p=new int(i); } } 假如创建对象时,执行new分配时失败了,便会出现bug,若代码量大,是很难找到这个问题的,这种便被称为半成品对象. 如何来避免半成品对象的诞生呢? 就要用到本章学的

  • C++深入探究二阶构造模式的原理与使用

    目录 一.构造函数的回顾 二.半成品对象 三.二阶构造 四.小结 一.构造函数的回顾 关于构造函数 类的构造函数用于对象的初始化 构造函数与类同名并且没有返回值 构造函数在对象定义时自动被调用 问题 如何判断构造函数的执行结果? 在构造函数中执行 return 语句会发生什么? 构造函数执行结束是否意味着对象构造成功? 下面看一个异常的构造函数: #include <stdio.h> class Test { int mi; int mj; bool mStatus; public: Test

  • JS常见构造模式实例对比分析

    本文实例分析了JS常见构造模式.分享给大家供大家参考,具体如下: 1.工厂模式 没有解决对象识别的问题.因为函数内部使用了new Object来创建对象 function Factory(name,age) { var o=new Object(); o.name=name; o.age=age; o.what=what;//用函数引用的方式消除重复创建相同函数的弊端,节省资源.函数引用可以修改this的指向,函数调用不可以! return o; } what=funciton() { aler

  • 从云数据迁移服务看MySQL大表抽取模式的原理解析

    摘要:MySQL JDBC抽取到底应该采用什么样的方式,且听小编给你娓娓道来. 小编最近在云上的一个迁移项目中被MySQL抽取模式折磨的很惨.一开始爆内存被客户怼,再后来迁移效率低下再被怼.MySQL JDBC抽取到底应该采用什么样的方式,且听小编给你娓娓道来. 1.1 Java-JDBC通信原理 JDBC与数据库之间的通信是通过socket完,大致流程如下图所示.Mysql Server ->内核Socket Buffer -> 客户端Socket Buffer ->JDBC所在的JV

  • Docker工作模式及原理详解

    如下图所示: 我们在使用虚拟机和docker的时候,就会出现这样一个疑问:Docker为什么比VM虚拟机快呢? 上面这张图就很客观的说明了这个问题 1.Docker有着比虚拟机更少的抽象层. 2.Docker利用的是宿主机的内核,VM需要的是Guest os. 所以说,新建一个容器的时候,docker不需要像虚拟机一样重新加载一个操作系统.虚拟机是加载Guest os(花费时间分钟级别),而docker利用的是宿主机的操作系统,省略了这个复杂的过程(花费时间秒级别). 搞清楚这些,我们再来看看对

  • 详解model.train()和model.eval()两种模式的原理与用法

    一.两种模式 pytorch可以给我们提供两种方式来切换训练和评估(推断)的模式,分别是:model.train() 和 model.eval(). 一般用法是:在训练开始之前写上 model.trian() ,在测试时写上 model.eval() . 二.功能 1. model.train() 在使用 pytorch 构建神经网络的时候,训练过程中会在程序上方添加一句model.train(),作用是 启用 batch normalization 和 dropout . 如果模型中有BN层(

  • Java Builder模式实现原理及优缺点解析

    Builder 模式中文叫作建造者模式,又叫生成器模式,它属于对象创建型模式,是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节.下图是建造者模式的通用类图: 在建造者模式中,有如下4种角色: Product:产品角色 Builder:抽象建造者,定义产品接口 ConcreteBuilder:具体建造者,实现Builder定义的接口,并且返回组

  • Redis cluster集群模式的原理解析

    redis cluster redis cluster是Redis的分布式解决方案,在3.0版本推出后有效地解决了redis分布式方面的需求 自动将数据进行分片,每个master上放一部分数据 提供内置的高可用支持,部分master不可用时,还是可以继续工作的 支撑N个redis master node,每个master node都可以挂载多个slave node 高可用,因为每个master都有salve节点,那么如果mater挂掉,redis cluster这套机制,就会自动将某个slave

  • 初步探究Python程序的执行原理

    1. 过程概述 Python先把代码(.py文件)编译成字节码,交给字节码虚拟机,然后虚拟机一条一条执行字节码指令,从而完成程序的执行. 2. 字节码 字节码在Python虚拟机程序里对应的是PyCodeObject对象. .pyc文件是字节码在磁盘上的表现形式. 3. pyc文件 PyCodeObject对象的创建时机是模块加载的时候,即import. Python test.py会对test.py进行编译成字节码并解释执行,但是不会生成test.pyc. 如果test.py加载了其他模块,如

  • PHP的MVC模式实现原理分析(一相简单的MVC框架范例)

    他们的工作原理大家应该也比较感兴趣,下面我说说一个mvc框架长什么样. 路由机制 在互联网我们都是通过url提供服务,因此不同的url有不同的服务.用户访问不同的页面也就获得了不同的服务.那么我们的服务是如何通过url来区分不同的服务呢. 我们的web程序就要通过url寻找到不同的文件,进行不同的业务逻辑处理.我们的路由机制就是根据url,寻找到对应的controller,和action,然后由action进行具体的业务逻辑处理. 一个简单的controller 复制代码 代码如下: //定义一

随机推荐