如何创建与使用单例模式
引言
在 编程领域,单例模式是一种广泛应用的设计模式,它确保一个类仅有一个实例,并为该实例提供一个全局访问点。单例模式在许多场景下都至关重要,比如日志记录器、数据库连接池等,这些组件在整个应用程序中只需要一个实例来维护状态或进行资源管理。本文将深入探讨如何创建和使用单例模式,并分析不同实现方式的优缺点。
C 语言实现单例模式
在 C 语言中,由于没有类的概念,我们可以通过结构体和函数指针来模拟单例模式。
1. 基于文件静态变量的实现
c
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
#include <stdio.h>
// 定义单例结构体
typedef struct Singleton {
int data;
// 其他成员变量
} Singleton;
// 获取单例实例的函数
Singleton* getSingleton();
#endif
// singleton.c
#include "singleton.h"
// 文件静态变量,确保单例唯一性
static Singleton singleton = {0};
Singleton* getSingleton() {
return &singleton;
}
在上述实现中,singleton 是一个文件静态变量,这意味着它在程序的生命周期内只存在一份实例。getSingleton 函数返回这个唯一实例的指针。
2. 这种实现的优缺点
- 优点:实现简单直接,利用了 C 语言文件静态变量的特性来保证单例性。
- 缺点:缺乏封装性,结构体的成员变量直接暴露在外,容易被误修改。而且如果需要在多线程环境下使用,还需要额外处理线程安全问题。
C++ 语言实现单例模式
饿汉式单例
1. 实现原理
饿汉式单例在程序启动时就创建单例对象,即在类加载阶段就完成实例化。
2. 代码示例
cpp
// SingletonEager.h
#ifndef SINGLETONEAGER_H
#define SINGLETONEAGER_H
class SingletonEager {
public:
// 获取单例实例的静态方法
static SingletonEager* getInstance();
private:
// 构造函数私有化,防止外部实例化
SingletonEager();
// 析构函数私有化
~SingletonEager();
// 禁用拷贝构造函数
SingletonEager(const SingletonEager& other) = delete;
// 禁用赋值运算符
SingletonEager& operator=(const SingletonEager& other) = delete;
static SingletonEager* instance;
};
#endif
// SingletonEager.cpp
#include "SingletonEager.h"
// 初始化静态成员变量,在程序启动时就创建实例
SingletonEager* SingletonEager::instance = new SingletonEager();
SingletonEager::SingletonEager() {
// 初始化操作
printf("SingletonEager constructed\n");
}
SingletonEager::~SingletonEager() {
// 清理操作
printf("SingletonEager destructed\n");
}
SingletonEager* SingletonEager::getInstance() {
return instance;
}
3. 优缺点
- 优点:实现简单,由于在程序启动时就创建实例,天然具备线程安全性。
- 缺点:无论是否使用该单例,程序启动时就会占用资源,如果单例对象创建成本高且可能不会被使用,会造成资源浪费。
懒汉式单例
1. 实现原理
懒汉式单例采用延迟加载机制,在第一次调用获取实例的方法时才创建单例对象。
2. 线程不安全的实现
cpp
// SingletonLazyUnsafe.h
#ifndef SINGLETONLAZYUNSAFE_H
#define SINGLETONLAZYUNSAFE_H
class SingletonLazyUnsafe {
public:
// 获取单例实例的静态方法
static SingletonLazyUnsafe* getInstance();
private:
// 构造函数私有化
SingletonLazyUnsafe();
// 析构函数私有化
~SingletonLazyUnsafe();
// 禁用拷贝构造函数
SingletonLazyUnsafe(const SingletonLazyUnsafe& other) = delete;
// 禁用赋值运算符
SingletonLazyUnsafe& operator=(const SingletonLazyUnsafe& other) = delete;
static SingletonLazyUnsafe* instance;
};
#endif
// SingletonLazyUnsafe.cpp
#include "SingletonLazyUnsafe.h"
SingletonLazyUnsafe* SingletonLazyUnsafe::instance = nullptr;
SingletonLazyUnsafe::SingletonLazyUnsafe() {
// 初始化操作
printf("SingletonLazyUnsafe constructed\n");
}
SingletonLazyUnsafe::~SingletonLazyUnsafe() {
// 清理操作
printf("SingletonLazyUnsafe destructed\n");
}
SingletonLazyUnsafe* SingletonLazyUnsafe::getInstance() {
if (instance == nullptr) {
instance = new SingletonLazyUnsafe();
}
return instance;
}
在多线程环境下,这种实现存在竞态条件问题,多个线程可能同时判断 instance 为 nullptr,进而各自创建一个实例,破坏单例的唯一性。
3. 线程安全的实现
cpp
// SingletonLazySafe.h
#ifndef SINGLETONLAZYSAVE_H
#define SINGLETONLAZYSAVE_H
#include <mutex>
class SingletonLazySafe {
public:
// 获取单例实例的静态方法
static SingletonLazySafe* getInstance();
private:
// 构造函数私有化
SingletonLazySafe();
// 析构函数私有化
~SingletonLazySafe();
// 禁用拷贝构造函数
SingletonLazySafe(const SingletonLazySafe& other) = delete;
// 禁用赋值运算符
SingletonLazySafe& operator=(const SingletonLazySafe& other) = delete;
static SingletonLazySafe* instance;
static std::mutex mtx;
};
#endif
// SingletonLazySafe.cpp
#include "SingletonLazySafe.h"
SingletonLazySafe* SingletonLazySafe::instance = nullptr;
std::mutex SingletonLazySafe::mtx;
SingletonLazySafe::SingletonLazySafe() {
// 初始化操作
printf("SingletonLazySafe constructed\n");
}
SingletonLazySafe::~SingletonLazySafe() {
// 清理操作
printf("SingletonLazySafe destructed\n");
}
SingletonLazySafe* SingletonLazySafe::getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new SingletonLazySafe();
}
}
return instance;
}
通过引入互斥锁 mtx,在创建实例前进行加锁操作,确保同一时间只有一个线程能创建实例,从而解决了线程安全问题。
4. 优缺点
- 优点:延迟加载,只有在真正需要时才创建实例,节省资源。
- 缺点:实现相对复杂,尤其是线程安全的版本,需要处理好锁的机制,否则可能出现性能问题或死锁。
C++11 局部静态变量实现
1. 实现原理
C++11 引入了线程安全的局部静态变量。利用这一特性,在函数中定义局部静态变量作为单例实例,只有在第一次调用函数时才会初始化该变量,并且初始化过程是线程安全的。
2. 代码示例
cpp
// SingletonLocalStatic.h
#ifndef SINGLETONLOCALSTATIC_H
#define SINGLETONLOCALSTATIC_H
class SingletonLocalStatic {
public:
// 获取单例实例的静态方法
static SingletonLocalStatic& getInstance();
private:
// 构造函数私有化
SingletonLocalStatic();
// 析构函数私有化
~SingletonLocalStatic();
// 禁用拷贝构造函数
SingletonLocalStatic(const SingletonLocalStatic& other) = delete;
// 禁用赋值运算符
SingletonLocalStatic& operator=(const SingletonLocalStatic& other) = delete;
};
#endif
// SingletonLocalStatic.cpp
#include "SingletonLocalStatic.h"
SingletonLocalStatic::SingletonLocalStatic() {
// 初始化操作
printf("SingletonLocalStatic constructed\n");
}
SingletonLocalStatic::~SingletonLocalStatic() {
// 清理操作
printf("SingletonLocalStatic destructed\n");
}
SingletonLocalStatic& SingletonLocalStatic::getInstance() {
static SingletonLocalStatic instance;
return instance;
}
3. 优缺点
- 优点:既实现了延迟加载,又保证了线程安全,代码简洁明了。
- 缺点:依赖 C++11 标准,如果项目需要兼容旧版本的 C++,则无法使用这种方式。
使用单例模式
在 C 和 C++ 中使用单例模式的方式基本相同,都是通过调用获取单例实例的函数或方法来获取唯一实例,然后使用该实例进行相关操作。
C 语言使用示例
c
#include "singleton.h"
int main() {
Singleton* s1 = getSingleton();
Singleton* s2 = getSingleton();
printf("s1 and s2 are the same instance: %s\n", (s1 == s2)? "true" : "false");
return 0;
}
C++ 使用示例
cpp
#include "SingletonEager.h"
#include "SingletonLazySafe.h"
#include "SingletonLocalStatic.h"
int main() {
SingletonEager* eager1 = SingletonEager::getInstance();
SingletonEager* eager2 = SingletonEager::getInstance();
printf("Eager Singleton: eager1 and eager2 are the same instance: %s\n", (eager1 == eager2)? "true" : "false");
SingletonLazySafe* lazy1 = SingletonLazySafe::getInstance();
SingletonLazySafe* lazy2 = SingletonLazySafe::getInstance();
printf("Lazy Singleton: lazy1 and lazy2 are the same instance: %s\n", (lazy1 == lazy2)? "true" : "false");
SingletonLocalStatic& local1 = SingletonLocalStatic::getInstance();
SingletonLocalStatic& local2 = SingletonLocalStatic::getInstance();
printf("Local Static Singleton: local1 and local2 are the same instance: %s\n", (&local1 == &local2)? "true" : "false");
return 0;
}
总结
创建和使用单例模式有多种方式,每种方式都有其独特的优缺点和适用场景。在选择实现方式时,需要综合考虑项目的需求,如是否需要多线程支持、资源使用情况以及对 C++ 标准的兼容性等因素。通过合理运用单例模式,可以提高代码的可维护性和可扩展性,使程序更加健壮和高效。希望本文能帮助读者深入理解并在实际项目中灵活运用单例模式。
更多推荐


所有评论(0)