核心说明:单例模式是C++面向对象编程中最基础、最常用的设计模式之一,属于"创建型模式"。其核心目标是 确保一个类在整个程序生命周期中,只存在一个实例对象,并提供一个全局唯一的访问入口,避免重复创建对象造成的资源浪费、数据不一致等问题。本笔记从基础概念、实现约束、常见版本、注意事项到实战场景,逐步拆解,适合C++初学者夯实面向对象基础,也可作为复习巩固的参考。
一、核心概念(必记)
-
定义:单例模式(Singleton Pattern)是一种面向对象设计模式,用于保证一个类仅有一个实例,并提供一个访问该实例的全局访问点,不允许外部随意创建该类的对象。
-
核心需求:解决"全局唯一对象"的访问问题,常见应用场景:日志器、配置管理器、数据库连接池、全局计数器等(这类对象只需一个,多实例会导致资源浪费或数据错乱)。
-
设计本质:通过C++的访问控制(private/public)和静态成员特性,垄断类的实例创建权,禁止外部实例化、拷贝,仅暴露唯一访问接口。
二、单例模式的实现约束(重中之重)
要实现一个标准的单例类,必须满足3个核心约束(缺一不可,否则会出现"伪单例"),这是单例模式的基础,必须牢记:
-
私有构造函数(含默认构造、带参构造):将构造函数设为private,禁止外部通过
new关键字创建实例,从根源上杜绝外部实例化。 -
私有拷贝构造函数和赋值运算符:禁止外部通过拷贝(
Singleton s = Singleton::getInstance())、赋值(s1 = s2)的方式创建新实例,避免出现多个实例。C++11及以上可直接用= delete禁用。 -
公有静态访问接口:提供一个静态成员函数(通常命名为
getInstance()),作为外部访问单例实例的唯一入口,该函数负责创建并返回单例实例。
三、常见实现版本(分场景使用,必练)
单例模式的实现主要分为两大类型:饿汉式(提前创建)和懒汉式(延迟创建),不同版本各有优劣,需根据实际场景选择,以下是C++中最常用的4个版本,附完整可运行代码和核心解析。
版本1:饿汉式单例(基础版,线程安全)
核心思想
程序启动时(全局静态变量初始化阶段),就主动创建单例实例,后续调用访问接口时,直接返回已创建的实例,无需动态创建。
完整代码
cpp
#include <iostream>
using namespace std;
// 饿汉式单例类
class Singleton {
private:
// 1. 私有构造函数(禁止外部实例化)
Singleton() {
cout << "饿汉式单例实例创建" << endl;
}
// 2. 私有拷贝构造和赋值运算符(禁止拷贝)
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 3. 私有静态成员(程序启动时初始化,全局唯一)
static Singleton instance;
public:
// 4. 公有静态访问接口(唯一入口)
static Singleton& getInstance() {
return instance;
}
// 测试方法(模拟实际功能)
void show() {
cout << "单例实例正在工作(饿汉式)" << endl;
}
};
// 关键:全局区初始化静态实例(程序启动时执行,仅一次)
Singleton Singleton::instance;
// 测试代码(可直接运行)
int main() {
// 只能通过getInstance()访问实例,无法new、无法拷贝
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
// 验证实例唯一性(地址相同即为同一实例)
cout << "s1地址:" << &s1 << endl;
cout << "s2地址:" << &s2 << endl;
s1.show();
return 0;
}
优缺点及适用场景
优点:实现简单,无需考虑线程安全(C++中全局静态变量的初始化是线程安全的),访问速度快,无延迟。
缺点:实例提前创建,若实例创建成本高(如加载大量配置、创建数据库连接),且程序全程未使用,会造成资源浪费。
适用场景:单例实例创建成本低、程序启动后一定会使用该实例(如简单的全局配置管理器)。
版本2:懒汉式单例(基础版,非线程安全)
核心思想
延迟加载:程序启动时不创建实例,第一次调用 getInstance() 时,才创建实例,后续调用直接返回,避免资源浪费。
完整代码
cpp
#include <iostream>
using namespace std;
class Singleton {
private:
Singleton() {
cout << "懒汉式单例实例创建" << endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 静态指针,初始化为nullptr(不提前创建实例)
static Singleton* instance;
public:
static Singleton* getInstance() {
// 第一次调用时创建实例
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
void show() {
cout << "单例实例正在工作(懒汉式基础版)" << endl;
}
// 可选:手动释放内存(避免内存泄漏)
static void destroyInstance() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
};
// 初始化静态指针为nullptr
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
cout << "s1地址:" << s1 << endl;
cout << "s2地址:" << s2 << endl;
s1->show();
Singleton::destroyInstance(); // 手动释放
return 0;
}
优缺点及适用场景
优点:延迟加载,避免资源浪费,实例仅在需要时创建。
缺点:非线程安全!多线程环境下,多个线程可能同时进入 if (instance == nullptr),导致创建多个实例,打破单例约束。
适用场景:仅适用于单线程环境(如简单的控制台程序),实际开发中(多线程)禁止使用。
版本3:懒汉式单例(线程安全版,推荐)
核心思想
在基础懒汉式的基础上,添加互斥锁(std::mutex),保证多线程环境下,只有一个线程能创建实例;同时采用"双重检查锁定(DCLP)",减少锁的开销,兼顾线程安全和效率。
完整代码
cpp
#include <iostream>
#include <mutex> // 需包含互斥锁头文件
using namespace std;
class Singleton {
private:
Singleton() {
cout << "线程安全版懒汉式单例实例创建" << endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* instance;
static mutex mtx; // 互斥锁,保证线程安全
public:
static Singleton* getInstance() {
// 双重检查锁定(DCLP):减少锁的开销
if (instance == nullptr) { // 第一次检查:避免每次调用都加锁
lock_guard<mutex> lock(mtx); // 加锁,确保同一时间只有一个线程进入
if (instance == nullptr) { // 第二次检查:防止多个线程同时通过第一次检查
instance = new Singleton();
}
}
return instance;
}
void show() {
cout << "单例实例正在工作(线程安全版)" << endl;
}
static void destroyInstance() {
lock_guard<mutex> lock(mtx); // 释放时也加锁,避免线程安全问题
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
mutex Singleton::mtx;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
cout << "s1地址:" << s1 << endl;
cout << "s2地址:" << s2 << endl;
s1->show();
Singleton::destroyInstance();
return 0;
}
关键注意点
-
互斥锁(
std::mutex):C++11及以上标准才支持,需包含<mutex>头文件。 -
双重检查锁定(DCLP):两次检查
instance == nullptr,第一次检查避免频繁加锁(提升效率),第二次检查确保唯一实例。 -
内存释放:手动调用
destroyInstance()释放内存,避免内存泄漏;也可结合智能指针(std::unique_ptr)自动管理内存(后续优化版会讲)。
适用场景:多线程环境(如服务器程序、多线程控制台程序),是实际开发中最常用的懒汉式版本。
版本4:现代C++懒汉式(局部静态变量版,最优)
核心思想
利用C++11的特性:局部静态变量的初始化是线程安全的------多个线程同时访问局部静态变量的初始化语句时,只会有一个线程执行初始化,其他线程阻塞等待。无需手动加锁,代码更简洁、安全。
完整代码
cpp
#include <iostream>
using namespace std;
class Singleton {
private:
Singleton() {
cout << "现代C++懒汉式单例实例创建" << endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
// 局部静态变量实现:第一次调用时初始化,仅初始化一次
static Singleton& getInstance() {
static Singleton instance; // 局部静态变量,线程安全(C++11及以上)
return instance;
}
void show() {
cout << "单例实例正在工作(现代C++版)" << endl;
}
};
int main() {
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
cout << "s1地址:" << &s1 << endl;
cout << "s2地址:" << &s2 << endl;
s1.show();
return 0;
}
优缺点及适用场景
优点:代码最简洁(无需管理静态指针、互斥锁),线程安全(依赖C++11特性),延迟加载,无内存泄漏(局部静态变量在程序结束时自动销毁)。
缺点:仅支持C++11及以上标准,若项目需兼容旧标准(如C++98),则无法使用。
适用场景:C++11及以上环境,是现代C++开发中最推荐的单例实现方式(简洁、安全、高效)。
四、常见问题与注意事项(易错点,必记)
-
避免"伪单例":必须同时禁用构造函数、拷贝构造、赋值运算符,否则外部可能通过拷贝、赋值创建新实例。
-
线程安全问题:仅饿汉式(全局静态变量)、线程安全懒汉式(加锁)、现代C++懒汉式(局部静态变量)支持多线程,基础懒汉式不可用于多线程。
-
内存泄漏:懒汉式(指针版)需手动释放内存(如调用
destroyInstance()),或使用智能指针优化;局部静态变量版无需手动释放。 -
静态成员初始化:饿汉式的静态实例需在类外初始化,否则会编译报错。
-
不能滥用单例:单例模式会增加类的耦合度,若一个类不需要全局唯一实例,切勿强行使用单例(如普通的工具类)。
五、实战应用场景(结合C++面向对象)
单例模式的核心价值是"全局唯一",以下是C++开发中最常见的实战场景,可直接参考使用:
-
日志器(Logger):整个程序只需一个日志器实例,负责所有模块的日志输出,避免多个日志器同时写入导致日志错乱。
-
配置管理器(ConfigManager):读取配置文件(如config.ini),全局提供配置参数(如端口号、数据库地址),避免重复读取配置文件。
-
数据库连接池(DBConnectionPool):管理数据库连接,避免频繁创建、销毁连接造成的性能损耗,确保连接资源唯一。
-
全局计数器:统计程序中某个事件的发生次数(如接口调用次数),全局唯一计数,避免多实例计数混乱。
六、总结(核心要点提炼)
-
单例模式核心:唯一实例 + 全局唯一访问入口。
-
实现约束:私有构造、私有拷贝/赋值、公有静态访问接口。
-
常用版本选择:
-
简单场景、C++11以下:饿汉式。
-
多线程、C++11以下:线程安全懒汉式(加锁+DCLP)。
-
多线程、C++11及以上:现代C++懒汉式(局部静态变量,最优)。
- 注意:不滥用、防伪单例、防线程安全问题、防内存泄漏。