第一篇
一、什么是单例模式?
单例模式是一种常用的设计模式,它保证一个类只有一个实例,并且提供了全局访问该实例的方法。在C++中,单例模式的实现方式有多种。在单例模式中,通常使用一个静态方法或者一个静态变量来保存实例。这个静态方法或者静态变量可以被所有需要访问该实例的对象共享,并且在第一次调用时创建实例。之后每次调用该方法或者访问该变量时,都返回同一个实例。
二、单例模式的特点:
一个类只有一个实例
该实例在程序运行的整个周期内始终存在
该实例可以被全局访问
单例模式可以用于控制资源的访问,例如数据库连接池、线程池等。它还可以用来确保系统中某些组件只有一个实例,例如配置文件管理器、日志记录器等。
三、单利模式的创建方法
方法1:静态局部变量(推荐!!)--也是一种懒汉式(第一次调用时创建实例对象)
使用静态局部变量的方式实现单例模式是一种简洁且线程安全的方法。无需显式使用互斥锁或原子操作,能够在需要时按需创建单例实例,并且在整个程序生命周期内,保持单例的唯一性。
头文件:
class Singleton {
public:
static Singleton& getInstance();
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton();
};
源文件:
#include "Singleton.h"
//第一次调用时候,创建实例对象
Singleton& Singleton::getInstance() {
static Singleton instance;
return instance;
}
Singleton::Singleton() {
// 构造函数
}
在上述代码中,getInstance() 方法返回一个对静态局部变量 instance 的引用。静态局部变量在函数首次调用时被初始化,并且在整个程序生命周期内保持存在。由于静态局部变量在 C++ 中具有线程安全的保证,因此无需显式使用互斥锁或原子操作来保护实例的创建过程。
总结
以上就是C++单例模式的所有实现方式。不同的实现方式在性能、线程安全等方面有所区别,具体实现方式应该根据实际情况进行选择,同时,需要注意在多线程环境下进行线程安全的处理,推荐优先使用静态局部变量方式。
方法2:new方法创建对象
原文链接:https://blog.csdn.net/mataojie/article/details/130907411
2.1 饿汉式:
特点:实现简单,线程安全,但可能造成内存浪费
适用情况: 单例对象在程序运行过程中频繁被调用
饿汉式是最简单的一种单例模式实现方式,它在程序启动时就创建了单例对象,因此也被称为“急切创建”方式。这种方式的优点是实现简单且线程安全,因为这种方式在单例对象被使用之前就已经创建好了,因此不存在多线程环境下的竞争问题,但是缺点是如果该对象很少被使用,会造成内存浪费。
饿汉式单例模式的实现方式一般是将单例模式定义为静态成员变量,并在类定义中就初始化它,这样单例对象就会在类装载的时候进行创建,在整个程序结束时按序销毁。
具体实现如下:
class Singleton {
public:
static Singleton* getInstance();
private:
Singleton();
~Singleton();
Singleton(const Singleton &signal);
const Singleton &operator=(const Singleton &signal);
private:
// 唯一的单例对象
static Singleton *instance_;
};
// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::instance_= new (std::nothrow) Singleton();
Singleton* Singleton::getInstance() {
return instance_;
}
Singleton::Singleton() {}
Singleton::~Singleton() {}
在上面的示例代码中,Singleton类的instance_成员变量是一个静态成员变量,它被定义为私有的,只能通过getInstance()方法来访问。getInstance()方法返回的是instance_的引用,通过这种方式来保证只有一个实例被创建。由于instance_被定义为静态成员变量,它会在程序启动时就被初始化。由于该方式在程序启动时就创建了单例对象,因此被称为“饿汉式单例模式”。
2.2 懒汉式
1)特点:延迟创建对象实例,避免了不必要的资源消耗,但是在多线程环境中线程不安全,需要加锁保证线程安全。
不是线程安全的原因:这是因为C++中构造函数并不是线程安全的!
C++中的构造函数简单来说分两步:
第一步:内存分配
第二步:初始化成员变量
由于多线程的关系,可能当我们在分配内存好了以后,还没来得急初始化成员变量,就进行线程切换,另外一个线程拿到所有权后,由于内存已经分配了,但是变量初始化还没进行,因此“使用成员变量”的相关值会发生不一致现象。
2)适用情况: 单例对象的创建和初始化过程比较耗时,而且在程序运行过程中可能并不总是需要使用该对象,对资源敏感时也不叫使用,如果线程安全没什么要求,也可以用。
懒汉式是另一种常见的单例模式实现方式,它在第一次访问单例对象时才进行创建。具体实现如下:
头文件Singleton.h:
class Singleton {
private:
// 私有的构造函数,防止外部创建对象
Singleton();
// 单例对象的指针
static Singleton* instance;
public:
// 获取单例对象的静态方法
static Singleton* getInstance();
};
源文件Singleton.cpp
#include "Singleton.h"
Singleton* Singleton::instance = nullptr;
//构造函数
Singleton::Singleton() {
//1.分配内存空间
//2.进行内存初始化操作
}
//第一次调用getInstance时候,创建对象
Singleton* Singleton::getInstance() {
if (instance == nullptr) {
instance = new Singleton();//创建对象
}
return instance;
}
上述代码中,Singleton类的构造函数是私有的,防止外部直接创建对象。instance指针被初始化为nullptr。在getInstance()方法中,首先检查instance是否为nullptr,如果是,则说明还没有创建单例对象,需要进行创建。创建成功后,将其赋值给instance指针,并返回该指针。`
注意 :懒汉式单例模式的特点是在第一次请求时才创建对象,避免了程序启动时的资源浪费,但需要注意在多线程环境下的线程安全性。以上示例是简单的单线程示例,在多线程环境下需要添加线程安全的措施,比如使用互斥锁或双重检查锁定等机制来保证线程安全性。
2.3 双重检查锁
C++中的双检锁(Double-Checked Locking)实现单例模式是一种在多线程环境下延迟创建单例对象的方式,通过使用双重检查来提高性能。
头文件:
class Singleton {
public:
// 获取单例实例的静态方法
static Singleton* getInstance();
// 删除拷贝构造函数和赋值运算符重载,确保单例的唯一性
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
// 私有构造函数,防止外部实例化
Singleton();
static Singleton* instance; // 单例实例指针
static std::mutex mutex; // 互斥锁
};
源文件:
#include "Singleton.h"
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
Singleton::Singleton() {
// 构造函数
}
Singleton* Singleton::getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mutex); // 加锁
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
注意: 双检锁(Double-Checked Locking)在某些情况下可能不是线程安全的,尤其是在特定的编译器和硬件平台上。这是由于编译器和处理器的优化行为可能导致双检锁失效,从而导致多个线程同时创建实例。
具体来说,双检锁的问题源于指令重排序(instruction reordering)和多核处理器的内存可见性(memory visibility)。编译器和处理器为了提高执行效率,可能会对代码中的指令进行重排序,而不考虑程序员的意图。这种重排序可能会导致在检查 instance 是否为 nullptr 之后,但在实际创建实例之前,另一个线程就已经读取到了一个尚未完全初始化的实例。
第二篇:
一、单例模式:
1.CFsSystem类的构造函数,写成私有的CFsSystem::CFsSystem(void):防止在类的外部,创建系统对象;
class CPsSystem
{
//注意:因为class的构造函数默认是私有。这里不写“访问权限”,默认是private的
CPsSystem(void); //因这里是私有构造函数,所以外面不能调用构造函数 创建类对象
构造函数内,什么都不做。
};
2.单例模式有且仅有一个对象,在CFsSystem类内的提供一个static的方法:用于对外创建自身的唯一对象。static CFsSystem obj;(会隐式调用CFsSystem的构造函数);
好处:避免内存、连接等资源的浪费,比如配置文件,就一份,可共享;
CFsSystem* CFsSystem::GetInstance()
{
static CFsSystem obj;//静态局部变量,只会初始化一次
return &obj;
}
二、简单单例模式的使用
一共有两点:
1. 构造函数必须是private的,如果不是private的,外部对象就可以创建,就可以多个,即不属于单例模式。单例模式要求必须一个。
2. 类必须提供至少一个static的方法,用于对外创建自身的唯一对象。(本身控制,判断指针是否为空,为空创建,不为空则不创建,体现在唯一上。)
简单总结:构造函数private + 方法static =》 保唯一
单例模式的好处是唯一,避免内存、连接等资源的浪费,比如配置文件,就一份,可共享。
举例:
int Init()
{
m_pAppCfg = StAppConfig::getInstance();
if (NULL == m_pAppCfg)
{
return -1;
}
return 0;
}
class StAppConfig
{
public:
static StAppConfig * getInstance()
{
if (NULL == m_pCfg)
{
//指向自身对象,目的仅创建一次对象,即单例模式。
m_pCfg = new StAppConfig ();
}
//返回指向自身的一个指针,返回创建的单例模式的地址
return m_pCfg;
}
private:
StAppConfig():m_pCfg(NULL){};
StAppConfig * m_pCfg;
}
变量初始化的方式有两种:
int i=0;
int i;
i=0;
StAppConfig tmp1; x错误 。
StAppConfig tmp2; x错误 。
因为创建对象的时候,一定会调用构造函数。 不论是缺省的还是自实现的,一定要调用。
又因为构造函数是私有的,所以不能创建对象(实例),没有实例就不能调用类中的普通方法(非static方法)
加上static后,就变成了全局类方法。可以通过以下两种方法进行使用。
1. 类名+::+ static方法 [必须是static的]
2. 对象.或->static方法 [一般使用方式1]
StAppConfig * tmp1=StAppConfig:: getInstance() ; //对象的指针变量
StAppConfig * tmp2=StAppConfig:: getInstance() ;
这种做法正确。 指针是地址的指向,并没有创建对象。真正的创建对象是getInstance来创建的。
为什么getInstance可以创建?类成员方法,只能对象来调。
引用是别名的概念,引用必须指向同一种类型,并且只能赋值一次。
指针是同类型之间任意变量指向。
方法2: