C++之单例模式

这篇具有很好参考价值的文章主要介绍了C++之单例模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

单例模式(Singleton Pattern)是 面向对象中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

优点:通过单例模式的设计,使得创建的类在当前进程中只有一个实例,并提供一个全局性的访问点,这样可以规避因频繁创建对象而导致的 内存飙升 情况。

介绍

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

1、单例模式是什么?

在面向对象编程中,有时候我们希望一个类只有一个实例化的对象,比如线程池,缓存等。这些类有且只有一个唯一的实例,这种设计模式被称为单例模式。

1.1 实现单例模式的三个要点

1)私有化构造函数:这样外界就无法自由地创建类对象,进而阻止了多个实例的产生。
2)类定义中含有该类的唯一静态私有对象:静态变量存放在全局存储区,且是唯一的,供所有对象使用。
3)用公有的静态函数来获取该实例:提供了访问接口。

1.2 单例模式分类

单例模式有两种主要实现方法:懒汉模式和饿汉模式。

  1. 懒汉模式特点是当外界调用时才进行实例化;
  2. 饿汉模式特点是一开始就对实例进行初始化,调用时直接返回这个构建好的实例。

2. 懒汉式

懒汉模式特点是当外界调用时才进行实例化。

2.1 懒汉实现:基础方法

是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
缺点:一个是线程安全,另一个是内存泄漏。
线程安全是因为在多线程场景下,有可能出现多个线程同时进行new操作的情况,没通过加锁来限制。
内存泄漏是因为使用了new在堆上分配了资源,那么在程序结束时,也应该进行delete,确保堆中数据释放。

public class Singleton {  
    // 静态私有对象
    private static Singleton instance;  
    // 私有构造函数
    private Singleton (){}  
  
  // 公有接口获取唯一实例
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

这种方式在单线程下没有问题,但是如果多线程模式下,当唯一实例还没有创建,两个线程同时调用getinstance就可能同时创建对象,导致错误。

2.2 懒汉实现:基于单锁

是否多线程安全:是
实现难度:较易
描述:这种方式采用单锁机制,有可能造成阻塞。

Singleton*Singleton::getInstance(){
    m.lock();
    if (_instance == nullptr)
        _instance = new Singleton;
    m.unlock();
    return _instance;
}

加锁又会带来另外的性能问题,如果每个线程每次获取实例都加锁,有可能造成阻塞的发生。实际上,上锁的目的是为了防止有多个线程在实例未被初始化的情况下,同时对他进行初始化,如果实例已经被创建了,就不需要考虑这个问题了,所以就可以采用二次加锁的方法来提高程序的性能。

2.3 懒汉实现:基于双重检测锁

是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,可以确保线程安全,且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。

Singleton*Singleton::getInstance(){

    if (_instance == nullptr)
    {
        m.lock();
        if (_instance == nullptr)
        {
            _instance = new Singleton;
        }
        m.unlock();
    }
    return _instance;
}

接下来,我们再解决内存泄漏(资源释放)问题,对懒汉式实现进行进一步的改进。

2.4 懒汉实现:基于双重检测锁和资源管理

是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,可以确保线程安全,且在多线程情况下能保持高性能。并且加入资源管理机制,以达到对资源的释放的目的。
我们加入资源管理机制,以达到对资源的释放的目的,解决方法有两个:智能指针&静态嵌套类。

2.4.1 智能指针方式

将实例指针更换为智能指针,另外智能指针在初始化时,还需要人为添加公有的毁灭函数,因为析构函数私有化了。

#include <iostream>
#include <mutex>
using namespace std;

// 单例模式演示类
class Singleton
{
public:
// 公有接口获取唯一实例
	static shared_ptr<Singleton> getInstance() {
// 若为空则创建
		if (instance == nullptr) {
// 加锁保证线程安全
// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞
// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕
		lock_guard<mutex> l(m_mutex);
		if (instance == nullptr) {
			cout << "实例为空,开始创建。" << endl;
			instance.reset(new Singleton(), destoryInstance);
			cout << "地址为:" << instance << endl;
			cout << "创建结束。" << endl;
		}
	}
		else {
			cout << "已有实例,返回。" << endl;
		}
	return instance;
}
// 毁灭实例
static void destoryInstance(Singleton* x) {
	cout << "自定义释放实例" << endl;
	delete x;
}

private:
// 私有构造函数
Singleton() {
	cout << "构造函数启动。" << endl;
};

// 私有析构函数
~Singleton() {
	cout << "析构函数启动。" << endl;
};

private:
// 静态私有对象
static shared_ptr<Singleton> instance;
// 锁
static mutex m_mutex;
};

// 初始化
shared_ptr<Singleton> Singleton::instance;
mutex Singleton::m_mutex;

应用智能指针后,在程序结束时,它自动进行资源的释放,解决了内存泄漏的问题。

2.4.2 静态嵌套类方式

类中定义一个嵌套类,初始化该类的静态对象,当程序结束时,该对象进行析构的同时,将单例实例也删除了。

#include <iostream>
#include <mutex>
using namespace std;

// 单例模式演示类
class Singleton
{
public:
// 公有接口获取唯一实例
static Singleton* getInstance() {
// 若为空则创建
	if (instance == nullptr) {
// 加锁保证线程安全
// 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞
// 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕
		lock_guard<mutex> l(m_mutex);
		if (instance == nullptr) {
			cout << "实例为空,开始创建。" << endl;
			instance = new Singleton();
			cout << "地址为:" << instance << endl;
			cout << "创建结束。" << endl;
			}
	}
		else {
			cout << "已有实例,返回。" << endl;
			}
	return instance;
}

private:
// 私有构造函数
Singleton() {
	cout << "构造函数启动。" << endl;
	};

// 私有析构函数
~Singleton() {
cout << "析构函数启动。" << endl;
};

// 定义一个删除器
class Deleter {
public:
Deleter() {};
~Deleter() {
if (instance != nullptr) {
	cout << "删除器启动。" << endl;
	delete instance;
	instance = nullptr;
	}
}
};

// 删除器是嵌套类,当该静态对象销毁的时候,也会将单例实例销毁
static Deleter m_deleter;
private:
// 静态私有对象
static Singleton* instance;
// 锁
static mutex m_mutex;
};

// 初始化
Singleton* Singleton::instance = nullptr;
mutex Singleton::m_mutex;
Singleton::Deleter Singleton::m_deleter;

2.5 懒汉实现:基于局部静态对象

是否多线程安全:是
实现难度:一般
描述:C++11后,规定了局部静态对象在多线程场景下的初始化行为,只有在首次访问时才会创建实例,后续不再创建而是获取。若未创建成功,其他的线程在进行到这步时会自动等待。注意C++11前的版本不是这样的。

因为有上述的改动,所以出现了一种更简洁方便优雅的实现方法,基于局部静态对象实现。

#include <iostream>
#include <mutex>
using namespace std;

// 单例模式演示类
class Singleton
{
public:
// 公有接口获取唯一实例
static Singleton& getInstance() {
	cout << "获取实例" << endl;
	static Singleton instance;
	cout << "地址为:" << &instance << endl;
	return instance;
	}
private:
// 私有构造函数
Singleton() {
	cout << "构造函数启动。" << endl;
	};

// 私有析构函数
~Singleton() {
	cout << "析构函数启动。" << endl;
	};
};

3. 饿汉式

饿汉模式特点是一开始就对实例进行初始化,调用时直接返回这个构建好的实例。

3.1 饿汉实现:基础方法

是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。第一次调用才初始化,避免内存浪费。
缺点:类加载时就初始化,浪费内存。必须加锁 synchronized 才能保证单例,但加锁会影响效率。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

#include <iostream>
#include <mutex>
using namespace std;

// 单例模式演示类
class Singleton
{
public:
// 公有接口获取唯一实例
static Singleton* getInstance() {
	cout << "获取实例" << endl;
	cout << "地址为:" << instance << endl;
	return instance;
	}
private:
// 私有构造函数
Singleton() {
	cout << "构造函数启动。" << endl;
};

// 私有析构函数
~Singleton() {
	cout << "析构函数启动。" << endl;
};

private:
// 静态私有对象
static Singleton* instance;
};

// 初始化
Singleton* Singleton::instance = new Singleton();

main还没开始,实例就已经构建完毕,获取实例的函数也不需要进行判空操作,因此也就不用双重检测锁来保证线程安全了,它本身已经是线程安全状态了。
但是内存泄漏的问题还是要解决的,这点同懒汉是一样的。可以通过智能指针和静态嵌套实现。

4. 总结

一般情况下,建议使用基于双重检测锁和资源管理搭配智能指针的懒汉方式。文章来源地址https://www.toymoban.com/news/detail-816699.html

到了这里,关于C++之单例模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java之单例模式

    目录 一.上节内容 1.什么是线程安全 2.线程不安全的原因 3.JMM(Java内存模型) 4.synchronized锁 5.锁对象 6.volatile 7.wait()和notify() 8.Java中线程安全的类 二.单例模式 1.什么是单例 2.怎么设计一个单例 1.口头约定 2.使用编程语言的特性 三.饿汉模式 四.懒汉模式 1.单线程下的懒汉模

    2024年02月04日
    浏览(51)
  • 线程安全之单例模式

    这篇文章,我们会介绍一下单例模式,但这里的单例模式,不是我们所说的设计模式,当然听到设计模式,大家一定都说,我当然知道设计模式了,有23种呢?一下子一顿输出,当然我这里说的单例模式还是跟设计模式有一些区别的,当然我不做概述,因为我也没咋个去了解过设计模式,我把

    2024年02月06日
    浏览(62)
  • 设计模式之单例模式(懒汉, 饿汉)

    单例模式是一种常用的软件设计模式, 该模式的主要目的是确保某一个类在内存中只能有一个实例对象, 通过单例模式的方法创建的类在当前进程中只有一个实例对象. 常见的单例模式有两种: 饿汉式, 这里的 “饿” 意义表述不够清晰, 用 “急” 来表述意义更加容易联想一些

    2024年02月22日
    浏览(52)
  • Java设计模式之单例模式

    定义:保证一个类仅有一个实例,并提供一个全局访问点 类型:创建型 想确保任何情况下都绝对只有一个实例 例如:线程池,数据库连接池一般都为单例模式 单例模式优点 在内存中只有一个实例,减少内存开销 可以避免对资源的多重占用 设置全局访问点,严格控制访问

    2024年02月02日
    浏览(72)
  • 设计模式之单例设计模式

    就是一个类只允许创建一个对象,那么我们称该类为单例类,这种设计模式我们称为单例模式。 资源共享:有些类拥有共享的资源,例如数据库连接池、线程池、缓存等。使用单例模式确保只有一个实例,避免资源浪费和竞争条件。 线程安全:单例模式可以用来保证多线程

    2024年02月07日
    浏览(75)
  • 浅谈设计模式之单例模式

    单例模式属于创建型模式,它提供了一种创建对象的最佳方式。单例模式指的是 单一的一个类 ,该类负责创建自己的对象,并且保证该 对象唯一 。该类提供了一种访问其唯一对象的方法,外部需要调用该类的对象可以通过方法获取,不需要实例化类的对象。 关键点: 单例

    2024年02月16日
    浏览(70)
  • C#--设计模式之单例模式

    单例模式大概是所有设计模式中最简单的一种,如果在面试时被问及熟悉哪些设计模式,你可能第一个答的就是单例模式。 单例模式的实现分为两种: 饿汉式:在静态构造函数执行时就立即实例化。 懒汉式:在程序执行过程中第一次需要时再实例化。 两者有各自适用的场景

    2024年02月14日
    浏览(45)
  • C#设计模式之单例模式

    单例模式(Singleton)保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式的结构图如下所示: 对一些类来说,只有一个实例是很重要的。如何才能保证一个类只有一个实例并且这个实例易于被访问呢? 基于程序员之间的约定或是利用全局变量吗? 虽然这样

    2024年02月03日
    浏览(51)
  • Unity设计模式之单例模式

    单例模式(Singleton)是设计模式中很常见的一种设计模式,目的是为了让一个类在程序运行期间有且仅有一个实例,且方便全局访问。 1、私有的构造函数。 2、含有一个该类的静态私有对象。 3、静态的公有函数或属性,方便用户创建或者获取它本身的静态私有对象。 当项目

    2023年04月09日
    浏览(71)
  • 万字解析设计模式之单例模式

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

    2024年02月08日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包