C++面试

单例模式的实现分析

2018-08-18  本文已影响2人  XDgbh
  1. 外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。
    内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件
  2. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
  3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
  4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
  5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
  7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,
    主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
  8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
  9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
  10. 单例模式常常与工厂模式结合使用,因为工厂只需要创建产品实例就可以了,在多线程的环境下也不会造成任何的冲突,因此只需要一个工厂实例就可以了。
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<mutex>
using namespace std;

/*
———————————— C语言下的单例模式 —————————————————
最简单的就是定义一个全局变量,这个全局变量在全局唯一代表一种东西
或者将所有的全局变量集中放在一个结构体中并且全局只有一个结构体对象globals
*/
struct C_singleton_globals
{
    int loop_flag;  //可以是程序中控制某个while死循环的终止条件
    char config_log_path; //可以是统一存放日志信息的位置
};
//全局唯一的结构体对象指针
typedef struct C_singleton_globals*  C_singleton_globals_t;
C_singleton_globals_t globals_t = NULL;
//若要放到堆内存中,可以写一个初始化函数分配内存
C_singleton_globals_t init_gobals_t()
{
    //如果还没有被初始化过,那就分配内存
    if (NULL == globals_t)
    {
        globals_t = (struct C_singleton_globals *)malloc(sizeof(struct C_singleton_globals));
        printf("为全局变量分配内存成功 !\n");
    }
    else
    {
        printf("为全局变量分配内存失败,之前已分配过 !\n");
    }
    return globals_t;
}

/*
——————————————————C++实现单例模式—————————————
1、将构造函数设计成private,使得不能随便产生新的对象
2、将唯一的实例对象设计成private下的static成员变量,static的作用是使得
//成员变成类拥有的,而不是某个实例对象拥有的
3、设计一个public的getInstance()方法,先检查是否实例过,
//若没有再分配内存实例出来一个对象
*/

//1、只适合在单线程环境下使用的代码,若是多线程,
//也可能刚好出现同时调用getInstance()函数,而导致产生多个实例
class singleton1
{
private:
    //私有构造函数,只能从类的成员函数来访问,不能从外部访问
    singleton1(){   }
    //为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,复制构造函数也要特别声明并置为私有。
    singleton1(const singleton1 &);
    //私有静态成员变量的声明
    static singleton1 *pInstance1;

public:
    static singleton1 * getInstance()
    {
        if (NULL == pInstance1)
        {
            pInstance1 = new singleton1();
            cout << "成功实例化pSingle1" << endl;
        }
        else
        {
            cout << "实例化pSingle1失败,已存在" << endl;
        }
        return pInstance1;
    }
};
//类的静态成员的定义(所以还需要指定类型)和赋值(类中只是声明),放在类的外部
//这句代码如果放到main函数中肯定会报错,要放在main外部
singleton1 * singleton1::pInstance1 = NULL;

//2、适合在多线程环境下使用的代码,增加线程互斥锁
class singleton2
{
private:
    //私有构造函数,只能从类的成员函数来访问,不能从外部访问
    singleton2(){   }
    //为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,
//复制构造函数也要特别声明并置为私有。
    singleton2(const singleton2 &);
    //私有静态成员变量的声明
    static singleton2 *pInstance2;
    //独占式互斥量,一段时间内仅一个线程可以访问
    static mutex mtx;   //设为static是为了给下面的static函数访问,则要在类定义后面初始化

public:
    static singleton2 * getInstance()
    {
        //增加这个判断(著名的DCL技法,即Double Check Lock双重锁定),
//可以不需要每次获取同一实例都先加锁再解锁,浪费资源
        if (NULL == pInstance2) 
        {
            mtx.lock(); //互斥加锁
            if (NULL == pInstance2)
            {
                pInstance2 = new singleton2();
                cout << "成功实例化pSingle2" << endl;
            }
            mtx.unlock();   //互斥解锁
        }
        else
        {
            cout << "实例化pSingle2失败,已存在" << endl;
        }
        return pInstance2;
    }
};
//类的静态成员的定义(所以还需要指定类型)和赋值(类中只是声明),放在类的外部
//这句代码如果放到main函数中肯定会报错,要放在main外部
singleton2 * singleton2::pInstance2 = NULL;
mutex singleton2::mtx;  //全局变量

//3、懒汉模式最佳实现代码,适用于多线程,无需加锁
class singleton3
{
private:
    //私有构造函数,只能从类的成员函数来访问,不能从外部访问
    singleton3(){   }
    //为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,
//复制构造函数也要特别声明并置为私有。
    singleton3(const singleton3 &);

public:
    static singleton3 * getInstance()
    {
        //C++11规定,在一个线程开始local static 对象的初始化后完成初始化前,
//其他线程执行到这个local static对象的初始化语句就会等待,
        //直到该local static 对象初始化完成。所以C++11标准下local static对象初始化在多线程条件下安全
        static singleton3 instance3;
        return &instance3;
    }
};
//以上都是懒汉单例模式,能拖多久就拖多久,即只有在第一次调用getInstance函数后才有实例产生。用时间换取空间—
//——————下面是饿汉单例模式,即程序一运行就立即产生实例,不管后面啥时候要用,甚至不用。用空间换取时间————
class singleton4
{
private:
    //私有构造函数
    singleton4(){ }
    //为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,复制构造函数也要特别声明并置为私有。
    singleton4(const singleton4 &);
    //私有静态指针声明
    static singleton4 *pInstance4;

public:
    static singleton4 * getInstance()
    {
        return pInstance4;
    }
};
//直接在给静态成员定义时,创建实例
//为何这里又可以直接调用私有构造函数呢?因为这里是全局环境,不属于任何函数内部调用(如main函数)
singleton4 * singleton4::pInstance4 = new singleton4();

//测试代码 
int main()
{
    C_singleton_globals_t p_g1 = init_gobals_t();
    C_singleton_globals_t p_g2 = init_gobals_t();
    singleton1 *ps1_1 = singleton1::getInstance();
    singleton1 *ps1_2 = singleton1::getInstance();
    //singleton1 s1_3;  //这句相当于调用singleton1()构造函数,只是开辟的栈内存,也不能外部调用构造函数
    //singleton1 *ps1_3 = new singleton1(); //构造函数私有,不能在外部调用
    //singleton1 ps1_3(*ps1_2);     //拷贝赋值操作创建实例副本,该构造函数也设为私有,也不能外部调用
    //singleton1 ps1_4 = *ps1_2;        //同上

    singleton2 *ps2_1 = singleton2::getInstance();
    singleton2 *ps2_2 = singleton2::getInstance();

    singleton3 *ps3_1 = singleton3::getInstance();
    singleton3 *ps3_2 = singleton3::getInstance();
    cout << "最经典懒汉模式返回函数局部静态实例指针,实例地址:" << ps3_1 << endl;
    cout << "最经典懒汉模式返回函数局部静态实例指针,实例地址:" << ps3_2 << endl;
    
    singleton4 *ps4_1 = singleton4::getInstance();
    singleton4 *ps4_2 = singleton4::getInstance();
    cout << "最经典恶汉模式返回类全局静态实例指针,实例地址:" << ps4_1 << endl;
    cout << "最经典恶汉模式返回类全局静态实例指针,实例地址:" << ps4_2 << endl;
}
上一篇 下一篇

猜你喜欢

热点阅读