服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++ 超详细深入分析单例模式

C++ 超详细深入分析单例模式

2022-10-28 13:10ymz123_ C/C++

单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建

不能被拷贝的类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

C++98

将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

?
1
2
3
4
5
6
7
8
class CopyBan
{
    //...
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};

原因:

设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了

只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

C++11

C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

?
1
2
3
4
5
6
7
class CopyBan
{
    //...
    CopyBan(const CopyBan&) = delete;
    CopyBan& operator = (const CopyBan&) = delete;
    //...
};

只能在堆上创建对象的类

实现方式:

将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。

提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HeapOnly
{
public:
    //提供一个static公有函数创建对象,对象创建的都在堆上
    static HeapOnly* CreateObject()
    {
        return new HeapOnly;
    }
private:
    HeapOnly(){}
 
    //C++98 防拷贝 只声明,不实现
    HeapOnly(const HeapOnly&);
    
    //C++11
    HeapOnly(const HeapOnly&) = delete;
};

只能在栈上创建对象的类

方法一:同上将构造函数私有化,然后设计静态方法创建对象返回即可。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
class StackOnly
{
public:
    StackOnly(){}
public:
    //C++11
    void* operator new(size_t size) = delete;
    void operator delete(void* p) = delete;
private:
    //C++98 防调用
    void* operator new(size_t size);
    void operator delete(void* p);
};

屏蔽new

因为new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉即可。注意:也要防止定位new

?
1
2
3
4
5
6
7
8
class StackOnly
{
public:
    StackOnly(){}
private:
    void* operator new(size_t size);
    void operator delete(void* p);
};

不能被继承的类

C++98

?
1
2
3
4
5
6
7
8
9
10
11
12
13
//C++98这种方式不够直接
//这里是可以继承的,但是Derive不能创建对象,因为Derive的构造函数必须要调用父类NonOnherit构造,但是NonInherit的构造函数私有了,私有在子类不可见,那么这里继承不会报错,继承的子类创建对象会报错
class NonInherit
{
public:
    static NonInherit GetInstance()
    {
        return NonInherit();
    }
private:
    NonInherit()
    {}
};

C++11

final关键字,final修饰类,表示该类不能被继承。

?
1
2
3
4
class A final
{
    //...
};

只能创建一个对象的类(单例模式)

设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

单例模式

定义一个全局对象,大家都能用,也能保证单例,但这种方式存在很大的缺陷,你要让大家都能用,这个对象就只能定义在一个.h,如果这个.h在多个.cpp包含,那么链接会报错。全局静态,只在当前文件可见,不再是同一个对象,每个xxx.cpp中各自是一个对象。 extern可以使链接不报错,但不能保证全局只有唯一一个v,可能某个地方又重新定义了一个变量v 所以我们可以在.h中声明,在.cpp中定义,声明和定义分离。否则在.h中定义,多个cpp包含就会有多份。

某些类, 只应该具有一个对象(实例), 就称之为单例.

在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据

单例模式有两种实现模式:饿汉实现方式和懒汉实现方式

[洗碗的例子]: 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭. 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式. 懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度.

饿汉模式

在main函数之前,一开始就创建对象

.h:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//饿汉模式:main函数之前,一开始就创建对象
//全局只要唯一的Singleton实例对象,那么他里面的成员也就是单例的
class Singleton
{
public:
    //3.提供一个获取单例对象的static成员函数
    static Singleton& GetInstance();
 
    //如果vector对象是私有,想访问,只能再封装一层
    //void PushBack(int x)
    //{
    //  _v.push_back(x);
    //}
    vector<int> _v;
private:
    //vector<int> _v;
 
    //1.构造函数私有化,不能随意创建对象
    Singleton()
    {}
 
    //防拷贝
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
 
    //2.类里面声明一个static Singleton对象,在cpp定义这个对象
    //保证全局只要一个唯一对象
    //这里的static类比的是全局变量,只是受类域的限制,没有改变链接属性
    static Singleton _sinst;
};

.cpp:

?
1
2
3
4
5
6
7
8
9
#include "Singleton.h"
 
//定义
Singleton Singleton::_sinst;
 
Singleton& Singleton::GetInstance()
{
    return _sinst;
}

优点:简单

缺点:main函数之前创建初始化的。如果单例对象的构造函数中要做很多工作,可能会导致进程启动慢。

且如果有多个单例类对象实例启动顺序不确定。

C++ 超详细深入分析单例模式

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

懒汉模式

饿汉式在应用启动时就创建了 实例,饿汉式是线程安全的,是绝对单例的。懒汉式在对外提供的获取方法被调用时会实例化对象。在多线程情况下,懒汉模式不是线程安全的。

第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//定义
Singleton* Singleton::_spinst = nullptr;
mutex Singleton::_mtx;
 
Singleton& Singleton::GetInstance()
{
    //双检查加锁 提高效率
    if (_spinst == nullptr)
    {
        _mtx.lock();
        if (_spinst == nullptr)
        {
            //第一次调用
            _spinst = new Singleton;
        }
        _mtx.unlock();
    }
 
    return *_spinst;
}
 
void Singleton::DelInstance()
{
    if (_spinst != nullptr)
    {
        _mtx.lock();
        if (_spinst != nullptr)
        {
            delete _spinst;
            _spinst = nullptr;
        }
        _mtx.unlock();
    }
}

C++ 超详细深入分析单例模式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#pragma once
#include <vector>
#include <iostream>
#include <mutex>
using namespace std;
 
//懒汉模式:第一次调用GetInstance时,才会创建初始化单例对象
//相对于饿汉,不存在可能会导致启动慢的问题,也可以控制顺序依赖的问题了
class Singleton
{
public:
    //3.提供一个获取单例对象的static成员函数
    static Singleton& GetInstance();
 
    //如果vector对象是私有,想访问,只能再封装一层
    //void PushBack(int x)
    //{
    //  _v.push_back(x);
    //}
    vector<int> _v;
 
        //或实现一个内嵌垃圾回收类
    class CGarbo {
    public:
        ~CGarbo() {
            if (Singleton::_spinst)
                delete Singleton::_spinst;
        }
    };
 
    //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
    static CGarbo Garbo;
    
private:
    //vector<int> _v;
 
    //1.构造函数私有化,不能随意创建对象
    Singleton()
    {}
 
    //防拷贝
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
 
    //2.类里面声明一个static Singleton对象,在cpp定义这个对象
    //保证全局只要一个唯一对象
    static Singleton* _spinst;
    static mutex _mtx;
};

到此这篇关于C++ 超详细深入分析单例模式的文章就介绍到这了,更多相关C++ 单例模式内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/mmz123_/article/details/123472438

延伸 · 阅读

精彩推荐
  • C/C++C++实现从数组中同时取出最大最小元素算法示例

    C++实现从数组中同时取出最大最小元素算法示例

    这篇文章主要介绍了C++实现从数组中同时取出最大最小元素算法,结合具体实例形式分析了C++通过数组的遍历、排序获取最大与最小元素的相关操作技巧,需...

    liyuxia7134852021-06-03
  • C/C++C语言 structural body结构体详解用法

    C语言 structural body结构体详解用法

    C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项,结构用于表示一条...

    Dark And Grey7722022-02-12
  • C/C++stl容器set,map,vector之erase用法与返回值详细解析

    stl容器set,map,vector之erase用法与返回值详细解析

    在使用 list、set 或 map遍历删除某些元素时可以这样使用,如下所示...

    C语言教程网5652020-12-31
  • C/C++C语言实现烟花表白程序代码

    C语言实现烟花表白程序代码

    大家好,本篇文章主要讲的是C语言实现烟花表白程序代码,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下...

    考拉爱睡觉鸭~7292022-09-15
  • C/C++C++并查集常用操作

    C++并查集常用操作

    并查集 是一种树型的数据结构,用于处理一些不相加集合的合并和查询问题。本文给大家分享C++并查集常用操作及算法实现,感兴趣的朋友跟随小编一起看...

    lihanyu1165732021-11-22
  • C/C++C语言三子棋游戏的简单设计

    C语言三子棋游戏的简单设计

    这篇文章主要为大家详细介绍了C语言三子棋游戏的简单设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    懂的都懂乀3942022-01-25
  • C/C++C++使用JsonCpp库操作json格式数据示例

    C++使用JsonCpp库操作json格式数据示例

    这篇文章主要介绍了C++使用JsonCpp库操作json格式数据,结合实例形式详细分析了JsonCpp库的下载及C++使用JsonCpp库对json格式数据序列化相关操作技巧,需要的朋友...

    ggjucheng10362021-05-17
  • C/C++c++ primer中的const限定符

    c++ primer中的const限定符

    这篇文章主要介绍了c++ primer中的const限定符,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考...

    小鹏7592021-09-07