C++面试 设计模式之单例模式(C++11)

2023-05-16

单例模式

确保一个类只有一个实例,并提供了一个全局访问点。

单例模式,可以说设计模式中最常应用的一种模式了,据说也是面试官最喜欢的题目。但是如果没有学过设计模式的人,可能不会想到要去应用单例模式,面对单例模式适用的情况,可能会优先考虑使用全局或者静态变量的方式,这样比较简单,也是没学过设计模式的人所能想到的最简单的方式了。

一般情况下,我们建立的一些类是属于工具性质的,基本不用存储太多的跟自身有关的数据,在这种情况下,每次都去new一个对象,即增加了开销,也使得代码更加臃肿。其实,我们只需要一个实例对象就可以。如果采用全局或者静态变量的方式,会影响封装性,难以保证别的代码不会对全局变量造成影响。

考虑到这些需要,我们将默认的构造函数声明为私有的,这样就不会被外部所new了,甚至可以将析构函数也声明为私有的,这样就只有自己能够删除自己了。在Java和C#这样纯的面向对象的语言中,单例模式非常好实现,直接就可以在静态区初始化instance,然后通过getInstance返回,这种就被称为饿汉式单例类。也有些写法是在getInstance中new instance然后返回,这种就被称为懒汉式单例类,但这涉及到第一次getInstance的一个判断问题。

构造函数声明为private或protect防止被外部函数实例化,内部保存一个private static的类指针保存唯一的实例,实例的动作由一个public的类方法代劳,该方法也返回单例类唯一的实例。

//单线程
//这是一个很棒的实现,简单易懂。但这是一个完美的实现吗?
//不!该方法是线程不安全的,考虑两个线程同时首次调用instance方法且同时检测到p是NULL值,
//则两个线程会同时构造一个实例给p,这是严重的错误!同时,这也不是单例的唯一实现!
class singleton
{
protected:
    singleton(){}
private:
    static singleton* p;
public:
    static singleton* instance();
};
singleton* singleton::p = NULL;
singleton* singleton::instance()
{
    if (p == NULL)
        p = new singleton();
    return p;
}

懒汉与饿汉

单例大约有两种实现方法:懒汉与饿汉。

懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上边的经典方法被归为懒汉实现;

饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。

特点与选择:

由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。

在访问量较小时,采用懒汉实现。这是以时间换空间。

线程安全的懒汉实现

线程不安全,怎么办呢?最直观的方法:加锁。
加锁的经典懒汉实现:

class singleton
{
protected:
    singleton()
    {
        pthread_mutex_init(&mutex);
    }
private:
    static singleton* p;
public:
    static pthread_mutex_t mutex;
    static singleton* initance();
};

pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;
singleton* singleton::initance()
{
    //因为每次判断是否为空都需要被锁定,如果有很多线程的话,就会造成大量线程的阻塞。
    //于是出现了双重锁定。
    if (p == NULL)
    {
        pthread_mutex_lock(&mutex);
        if (p == NULL)
            p = new singleton();
        pthread_mutex_unlock(&mutex);
    }
    return p;
}

内部静态变量的懒汉实现

此方法也很容易实现,在instance函数里定义一个静态的实例,也可以保证拥有唯一实例,在返回时只需要返回其指针就可以了。推荐这种实现方法,真得非常简单。  

class singleton
{
protected:
    singleton()
    {
        pthread_mutex_init(&mutex);
    }
public:
    static pthread_mutex_t mutex;
    static singleton* initance();
    int a;
};

pthread_mutex_t singleton::mutex;
singleton* singleton::initance()
{
    pthread_mutex_lock(&mutex);
    static singleton obj;
    pthread_mutex_unlock(&mutex);
    return &obj;
}

饿汉实现

因为饿汉实现本来就是线程安全的,不用加锁。

//假如有一个全局对象A 构造函数里引用上文中饿汉形式的指针,
//若在A构造函数构造之前以上单例并未构造出来,那就会有问题。
class singleton
{
protected:
    singleton()
    {}
private:
    static singleton* p;
public:
    static singleton* initance();
};
singleton* singleton::p = new singleton;
singleton* singleton::initance()
{
    return p;
}

常用的场景

单例模式常常与工厂模式结合使用,因为工厂只需要创建产品实例就可以了,在多线程的环境下也不会造成任何的冲突,因此只需要一个工厂实例就可以了。

优点

1.减少了时间和空间的开销(new实例的开销)。

2.提高了封装性,使得外部不易改动实例。

#ifndef _SINGLETON_H_
#define _SINGLETON_H_


class Singleton{
public:
    static Singleton* getInstance();

private:
    Singleton();
    //把复制构造函数和=操作符也设为私有,防止被复制
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);

    static Singleton* instance;
};

#endif


#include "Singleton.h"


Singleton::Singleton(){

}


Singleton::Singleton(const Singleton&){

}


Singleton& Singleton::operator=(const Singleton&){

}


//在此处初始化
Singleton* Singleton::instance = new Singleton();
Singleton* Singleton::getInstance(){
    return instance;
}


#include "Singleton.h"
#include <stdio.h>


int main(){
    Singleton* singleton1 = Singleton::getInstance();
    Singleton* singleton2 = Singleton::getInstance();

    if (singleton1 == singleton2)
        fprintf(stderr,"singleton1 = singleton2\n");

    return 0;
}

以上使用的方式存在问题:只能实例化没有参数的类型,其它带参数的类型就不行了。

c++11 为我们提供了解决方案:可变模板参数

template <typename T>
class Singleton
{
public:
template<typename... Args>
  static T* Instance(Args&&... args)
  {
        if(m_pInstance==nullptr)
            m_pInstance = new T(std::forward<Args>(args)...);
        return m_pInstance;
    }
  static T* GetInstance()
      {
            if (m_pInstance == nullptr)
                  throw std::logic_error("the instance is not init, 
                  please initialize the instance first");
            return m_pInstance;
      }
static void DestroyInstance()
    {
        delete m_pInstance;
        m_pInstance = nullptr;
    }

private:
        Singleton(void);
        virtual ~Singleton(void);
        Singleton(const Singleton&);
        Singleton& operator = (const Singleton&);
private:
    static T* m_pInstance;
};

template <class T> T*  Singleton<T>::m_pInstance = nullptr;

由于原来的接口中,单例对象的初始化和取值都是一个接口,可能会遭到误用,更新之后,讲初始化和取值分为两个接口,单例的用法为:先初始化,后面取值,如果中途销毁单例的话,需要重新取值。如果没有初始化就取值则会抛出一个异常。

Multiton

#include <map>
#include <string>
#include <memory>
using namespace std;

template < typename T, typename K = string>
class Multiton
{
public:
    template<typename... Args>
    static std::shared_ptr<T> Instance(const K& key, Args&&... args)
    {
        return GetInstance(key, std::forward<Args>(args)...);
    }

    template<typename... Args>
    static std::shared_ptr<T> Instance(K&& key, Args&&... args)
    {
        return GetInstance(key, std::forward<Args>(args)...);
    }
private:
    template<typename Key, typename... Args>
    static std::shared_ptr<T> GetInstance(Key&& key, Args&&...args)
    {
        std::shared_ptr<T> instance = nullptr;
        auto it = m_map.find(key);
        if (it == m_map.end())
        {
            instance = std::make_shared<T>(std::forward<Args>(args)...);
            m_map.emplace(key, instance);
        }
        else
        {
            instance = it->second;
        }

        return instance;
    }

private:
    Multiton(void);
    virtual ~Multiton(void);
    Multiton(const Multiton&);
    Multiton& operator = (const Multiton&);
private:
    static map<K, std::shared_ptr<T>> m_map;
};

template <typename T, typename K>
map<K, std::shared_ptr<T>> Multiton<T, K>::m_map;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++面试 设计模式之单例模式(C++11) 的相关文章

随机推荐

  • 【Linux operation 46】Centos 7.9中安装使用nmap

    1 nmap 介绍 nmap xff08 Network Mapper xff09 是一个开源的网络探测和安全扫描程序 nmap 的设计目标是快速地扫描大型网络 xff0c 当然用它扫描单个主机也没有问题 nmap 以新颖的方式使用原始 I
  • Nginx配置ssl证书

    1 下载证书 在ssl证书管理界面下载对应于Nginx的证书 下载的Nginx证书压缩文件解压后包含 xff1a pem xff1a 证书文件 PEM文件的扩展名为CRT格式 key xff1a 证书的密钥文件 申请证书时如果未选择自动创建
  • 0009基于51单片机智能门禁系统设计

    资料下载地址 单片机原理教程 初级 xff08 视频 xff0c 源仿真源代码 xff09 功能 60 可输入密码进行解锁 可以对IC卡进行注册或注销 可用已注册的IC卡解锁 可实现液晶显示当前时间 密码连续输错三次时能强制退出并报警 具有
  • 基于openstreetmap数据的SUMO路网生成路线

    一 一步到位 xff1a 基于SUMO自带工具smWebWizard py 使用SUMO自带的osmWebWizard py脚本 xff08 sumo tools xff09 进行下载 xff0c 脚本执行后会打开一个操作网页 xff0c
  • STM32烧写一次程序后,再次烧写识别不到单片机

    STM32cubeMX生成的程序 xff0c 编译后烧录一次后 xff0c 在次烧录会发现识别不到单片机了 xff0c 解决方案 xff1a 在STM32cubeMX工程中的SYS中 xff0c Debug中选择Serial Wire 使能
  • 百度超级链学院开课啦!第一讲教你《如何快速建链》

    百度超级链Xuperchain开源之后 xff0c 我们感受到了开发者伙伴们的热情关注 xff0c 其中有不少朋友提到希望进一步了解百度超级链网络的搭建方法 今天 xff0c 百度超级链小X姐姐和百度资深研发工程师静姐姐 xff0c 为大家
  • centos7操作/关闭防火墙

    前言 xff1a 测试环境部署不同端口的服务 xff0c 需要频繁使用防火墙放行端口 xff0c 比较麻烦 xff0c 所以需要彻底关闭防火墙 xff0c 整理了以下命令 xff0c 提供使用 systemctl status firewa
  • [kubernates]kube-flannel-ds 一直 CrashLoopBackOff

    使用 Kubeadm 安装 K8s 集群 xff0c 在安装 flannel 网络插件后 xff0c 发现 kube flannel ds 一直处于 CrashLoopBackOff 状态 xff1a span class token pu
  • Bottom-up And Top-down

    Bottom up 自下而上的处理可以理解为 xff1a 将感应器结果作为输入 xff0c 也就是激励 因此自下而上可以被描述为是数据驱动的 例如 xff0c 在一个人的花园正中有一朵花儿 xff0c 这个花儿的视觉和所有的激励信息都从视网
  • pyqt5在statusbar中不断的刷新显示不同的信息

    在PYQT5中不断的显示新的信息 需求 xff1a 需要在statusbar 上不断的显示新的测试数据 现在在测试例子中 xff0c 数据由numpy生成 总结如下 xff1a span class token comment coding
  • 如何在VS下调试自己写的dll

    一 准备资料 1 qt写的应用程序 test pro xff0c 编译程序可执行文件test exe 2 qt写的动态库程序 dll pro 二 调试步骤 1 用vs打开dll pro 2 dll工程右键属性如下图所示修改 3 编译运行dl
  • 通俗理解网络架构搜索(NAS)

    什么是NAS 我们假设模型必须是一个三层的全连接神经网络 xff08 一个输入层 一个隐层 一个输出层 xff09 xff0c 隐层可以有不同的激活函数和节点个数 xff0c 假设激活函数必须是relu或sigmoid中的一种 xff0c
  • GDB 的进入和退出

    进入和退出GDB 本节讨论如何启动和退出GDB 主要包括 xff1a 输入 39 gdb 进入GDB调试器输入quit或者按下Ctrl d退出调用GDB xff1a 如何启动GDB退出GDB xff1a 如何退出GDBShell脚本命令 x
  • OpenGL 矩阵变换GLM库的使用

    GLM和MVP矩阵操作速记 连续工作15小时 xff0c 累了 xff0c 睡觉 include glm glm hpp include glm gtc matrix transform hpp 若未特别说明 xff0c 以下示例均假设矩阵
  • 通俗理解RNN

    全连接神经网络和卷积神经网络他们都只能单独的取处理一个个的输入 xff0c 前一个输入和后一个输入是完全没有关系的 但是 xff0c 某些任务需要能够更好的处理序列的信息 xff0c 即前面的输入和后面的输入是有关系的 比如 xff0c 当
  • 基于深度学习的视频检测(一)

    一 简介 图像目标检测任务在过去几年深度学习的发展背景下取得了巨大的进展 xff0c 检测性能得到明显提升 但在视频监控 车辆辅助驾驶等领域 xff0c 基于视频的目标检测有着更为广泛的需求 由于视频中存在运动模糊 xff0c 遮挡 xff
  • 硬核解读 | 一篇文章看透百度XuperChain系统架构

    本期 百度超级链学院 邀请来资深研发工程师 xff0c 为各位开发者带来超硬核解读 xff0c 揭秘百度XuperChain系统架构到底是怎样的 xff01 背景 百度XuperChain在2019年5月底正式宣布开源 在开源后很快获得了开
  • 基于深度学习的视频检测(三) 目标跟踪

    搭建环境 Ubuntu16 04 43 CUDA9 43 cudnn7 43 python3 5 43 源码编译Tensorflow1 4 43 opencv3 3 基于 darkflow yolo v2 和 sort deep sort
  • 基于深度学习的视频检测(六) 行人计数,监控视频人员管理

    搭建 darkflow与 sort deep sort 环境 修改 darkflow net yolov2 predict py span class hljs keyword import span numpy span class hl
  • C++面试 设计模式之单例模式(C++11)

    单例模式 确保一个类只有一个实例 xff0c 并提供了一个全局访问点 单例模式 xff0c 可以说设计模式中最常应用的一种模式了 xff0c 据说也是面试官最喜欢的题目 但是如果没有学过设计模式的人 xff0c 可能不会想到要去应用单例模式