文章目录
- 前言
- [一、 设计一个不能被拷贝的类](#一、 设计一个不能被拷贝的类)
-
- [1. C++98 实现方式](#1. C++98 实现方式)
- [2. C++11 实现方式](#2. C++11 实现方式)
- 二、设计一个只能在堆上创建对象的类
-
- [1. 方法一:析构函数私有,提供destory接口释放资源](#1. 方法一:析构函数私有,提供destory接口释放资源)
- [2. 方法二:构造函数私有](#2. 方法二:构造函数私有)
- [三、 设计一个只能在栈上创建对象的类](#三、 设计一个只能在栈上创建对象的类)
-
- [1. 实现方式](#1. 实现方式)
- 四、设计一个不能被继承的类
-
- [1. C++98 实现方式](#1. C++98 实现方式)
- [2. C++11 实现方式](#2. C++11 实现方式)
- 五、设计一个只能创建一个对象(单例模式)
-
- [1. 单例模式介绍](#1. 单例模式介绍)
- [2. 饿汉模式(Eager Singleton)](#2. 饿汉模式(Eager Singleton))
- [3. 懒汉模式(Lazy Singleton)](#3. 懒汉模式(Lazy Singleton))
- [4. 饿汉VS懒汉](#4. 饿汉VS懒汉)
- 总结
前言
今天我们一起来学习常见的特殊类怎么设计,以及了解什么是单例~
一、 设计一个不能被拷贝的类
拷贝发生在两种场景:拷贝构造函数和赋值运算符。因此,若要禁止拷贝,只需让该类不能调用这两个函数。
1. C++98 实现方式
- 方法 :将拷贝构造函数和赋值运算符 仅声明不定义 ,并设置为
private
。 - 原因 :
- 私有化拷贝构造和赋值运算符,防止外部访问。
- 仅声明不定义,确保该函数不会被调用,即使成员函数内部尝试拷贝也会报错。
cpp
class CopyBan {
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
};
2. C++11 实现方式
- C++11 提供
= delete
语法,显式删除默认拷贝构造和赋值运算符。
cpp
class CopyBan {
public:
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(const CopyBan&) = delete;
};
二、设计一个只能在堆上创建对象的类
1. 方法一:析构函数私有,提供destory接口释放资源
cpp
class HeapOnly
{
public:
void Destroy()
{
delete this;
}
private:
~HeapOnly()
{
//...
}
};
int main()
{
//HeapOnly hp1;
//static HeapOnly hp2;
HeapOnly* hp3 = new HeapOnly;
//delete hp3;
hp3->Destroy();
return 0;
}
2. 方法二:构造函数私有
步骤:
- 构造函数私有
- 提供静态成员函数创建对象
- 禁拷贝与赋值,防止利用拷贝创建栈上的对象
具体代码和使用如下:
cpp
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
private:
HeapOnly()
{
//...
}
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly& operator=(const HeapOnly& hp) = delete;
};
int main()
{
//HeapOnly hp1;
//static HeapOnly hp2;
//HeapOnly* hp3 = new HeapOnly;
HeapOnly* hp3 = HeapOnly::CreateObj();
//HeapOnly copy(*hp3);
return 0;
}
三、 设计一个只能在栈上创建对象的类
1. 实现方式
步骤:
- 类比只能在堆上创建对象的类,先将构造函数私有
- 提供静态成员函数构造,不同的是只能在堆上创建对象的类new来创造,这里传值返回
- 为了避免这种情况:StackOnly* copy = new StackOnly(s),这样的拷贝方式创建堆上的对象,但是我们又不能禁拷贝和赋值,因此需要禁掉
operator new
,这样来写void* operator new(size_t) = delete
具体代码如下:
cpp
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly st;
return st;
}
private:
StackOnly()
{
//...
}
// 对一个类实现专属operator new
void* operator new(size_t size) = delete;
};
int main()
{
//StackOnly hp1;
//static StackOnly hp2;
//StackOnly* hp3 = new StackOnly;
StackOnly hp3 = StackOnly::CreateObj();
StackOnly copy(hp3);
// new operator new + 构造
// StackOnly* hp4 = new StackOnly(hp3);
return 0;
}
四、设计一个不能被继承的类
1. C++98 实现方式
- 方法 :将构造函数
private
,防止继承。
cpp
class NonInherit {
public:
static NonInherit GetInstance() {
return NonInherit();
}
private:
NonInherit() {}
};
2. C++11 实现方式
- 方法 :使用
final
关键字。
cpp
class A final {
// 该类无法被继承
};
五、设计一个只能创建一个对象(单例模式)
1. 单例模式介绍
- 保证系统中某个类只有一个实例。
- 提供全局访问点。
- 应用场景:如全局配置管理。
2. 饿汉模式(Eager Singleton)
- 特点:程序启动时即创建实例。(main函数之前就创建)
- 优点:线程安全。
- 缺点:可能导致启动慢。
(1)饿汉模式的实现步骤
代码:
cpp
namespace hungry
{
class Singleton
{
public:
// 2、提供获取单例对象的接口函数, 返回静态成员变量
static Singleton& GetInstance()
{
return _sinst;
}
void func();
void Add(const pair<string, string>& kv)
{
_dict[kv.first] = kv.second;
}
void Print()
{
for (auto& e : _dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
private:
// 1、构造函数私有,防止外部直接创建对象
Singleton() {}
// 3、防拷贝:删除拷贝构造和赋值运算符,避免创建多个实例
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
map<string, string> _dict;
// 4、静态成员变量,在程序启动时就初始化
static Singleton _sinst;
};
// 5、在类外部定义静态实例,在main函数执行前已创建
Singleton Singleton::_sinst;
}
(2)代码解析
(1)构造函数私有化
cpp
Singleton() {}
- 目的是防止外部代码通过
new
关键字创建对象 ,确保Singleton
类只能在GetInstance()
方法中创建实例。
(2)提供静态方法 GetInstance()
cpp
static Singleton& GetInstance()
{
return _sinst;
}
- 通过静态方法返回单例对象的引用,确保所有地方访问的都是同一个实例。
(3)防拷贝
cpp
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
- 防止拷贝和赋值 ,避免创建多个
Singleton
实例。
(4)静态成员变量 _sinst
cpp
static Singleton _sinst;
- 程序启动时(
main()
执行前)就创建该实例,无论是否真的需要。
(3)饿汉模式的优缺点
✅ 优点
-
线程安全
- 静态成员
_sinst
在编译时创建,天然是线程安全的,无需额外同步措施(如mutex
)。
- 静态成员
-
实现简单
- 不需要加锁 ,避免了懒汉模式的
double-check
加锁复杂性。
- 不需要加锁 ,避免了懒汉模式的
-
访问速度快
- 由于实例在程序启动时就已经创建,访问时无延迟,直接返回。
❌ 缺点
-
浪费资源
- 如果该单例对象初始化内容很多,而程序运行期间根本没用到,就会浪费资源,降低程序启动速度。
-
难以控制对象创建顺序
-
如果多个单例对象存在依赖关系(如 A 依赖 B),可能会导致未定义行为。
-
例如:
cppclass A { static A a_instance; B b; // A 依赖 B }; class B { static B b_instance; A a; // B 依赖 A };
- 由于
_sinst
在编译期静态初始化,两个类的创建顺序是由编译器决定的,可能会出现 A 还未初始化,但 B 已经尝试访问 A 的问题。
- 由于
-
3. 懒汉模式(Lazy Singleton)
- 特点:第一次使用时创建实例。
- 优点:启动快,资源按需分配。
- 缺点:线程不安全,需要加锁。
懒汉模式(Lazy Singleton)的实现思路解析
懒汉模式是一种 单例模式 (Singleton Pattern)的实现方式,其特点是 延迟创建实例 ,即第一次使用时才创建对象,而不是程序启动时就初始化(像饿汉模式那样)。
(1)为什么使用懒汉模式?
懒汉模式的主要优点是延迟加载 ,适用于 对象创建成本较高、但并不是一定会用到的情况,比如:
- 数据库连接
- 日志管理
- 需要动态管理生命周期的单例(如缓存数据)
(2)代码解析
完整代码:
cpp
namespace lazy
{
class Singleton
{
public:
// 2、提供获取单例对象的接口函数
static Singleton& GetInstance()
{
if (_psinst == nullptr)
{
// 第一次调用 GetInstance 时创建单例对象
_psinst = new Singleton;
}
return *_psinst;
}
// 释放单例对象(用于手动释放或持久化数据)
static void DelInstance()
{
if (_psinst)
{
delete _psinst;
_psinst = nullptr;
}
}
void Add(const pair<string, string>& kv)
{
_dict[kv.first] = kv.second;
}
void Print()
{
for (auto& e : _dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
// 3、GC(垃圾回收)类,在程序结束时自动释放 Singleton
class GC
{
public:
~GC()
{
lazy::Singleton::DelInstance();
}
};
private:
// 1、构造函数私有,防止外部直接创建对象
Singleton()
{
// ...
}
~Singleton()
{
cout << "~Singleton()" << endl;
// map数据写到文件中(持久化)
FILE* fin = fopen("map.txt", "w");
for (auto& e : _dict)
{
fputs(e.first.c_str(), fin);
fputs(":", fin);
fputs(e.second.c_str(), fin);
fputs("\n", fin);
}
fclose(fin);
}
// 4、防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
map<string, string> _dict;
static Singleton* _psinst; // 指向单例对象的指针
static GC _gc; // 静态 GC 对象,自动释放 Singleton
};
// 5、静态成员变量初始化
Singleton* Singleton::_psinst = nullptr; // 初始化单例指针为空
Singleton::GC Singleton::_gc; // 在程序退出时自动释放 Singleton
}
int main()
{
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
// 添加数据
lazy::Singleton::GetInstance().Add({ "xxx", "111" });
lazy::Singleton::GetInstance().Add({ "yyy", "222" });
lazy::Singleton::GetInstance().Add({ "zzz", "333" });
lazy::Singleton::GetInstance().Add({ "abc", "333" });
// 打印数据
lazy::Singleton::GetInstance().Print();
// 修改数据
lazy::Singleton::GetInstance().Add({ "abc", "444" });
lazy::Singleton::GetInstance().Print();
// 不手动调用 DelInstance,程序结束时 GC 自动释放
return 0;
}
(1)懒加载(Lazy Initialization)
cpp
static Singleton& GetInstance()
{
if (_psinst == nullptr)
{
_psinst = new Singleton;
}
return *_psinst;
}
特点:
- _psinst 指针初始化为
nullptr
,意味着程序启动时不会创建实例。 - 首次调用
GetInstance()
时才创建Singleton
实例。 - 之后每次调用
GetInstance()
,返回的都是同一个对象。
(2)手动释放单例
一般来说,单例是不需要去释放的,
特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)(GC)
cpp
static void DelInstance()
{
if (_psinst)
{
delete _psinst;
_psinst = nullptr;
}
}
作用:
- 由于
Singleton
对象是动态创建的,所以需要手动释放。 DelInstance()
用于手动释放对象,当程序需要手动控制资源释放时可以调用。
(3)GC 机制(自动释放单例)
cpp
class GC
{
public:
~GC()
{
lazy::Singleton::DelInstance();
}
};
工作原理:
GC
是Singleton
内部的一个嵌套类。static GC _gc;
是一个 静态成员变量 ,它的 析构函数会在程序退出时被调用 ,从而自动释放Singleton
实例。
⚠ 为什么要加 GC
?
- 避免 内存泄漏 ,因为
Singleton
对象是new
出来的,程序退出时如果不手动delete
,就会发生泄漏。 - 确保
Singleton
在main()
结束时释放,不会影响其他对象析构的顺序。
(4)防止拷贝
cpp
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
- 防止 拷贝构造 和 赋值运算符,保证单例模式不被破坏。
(5)对象持久化
cpp
~Singleton()
{
cout << "~Singleton()" << endl;
// map数据写到文件中
FILE* fin = fopen("map.txt", "w");
for (auto& e : _dict)
{
fputs(e.first.c_str(), fin);
fputs(":", fin);
fputs(e.second.c_str(), fin);
fputs("\n", fin);
}
fclose(fin);
}
- 在
Singleton
的析构函数中,把_dict
数据写入文件,确保程序退出时数据不会丢失。


4. 饿汉VS懒汉
方式 | 线程安全 | 访问速度 | 资源消耗 | 适用场景 |
---|---|---|---|---|
饿汉模式 | ✅ 安全 | ✅ 快 | ❌ 可能浪费 | 频繁使用的单例对象(如日志、配置管理) |
懒汉模式 | ❌ 需加锁 | ❌ 访问有延迟 | ✅ 只在需要时创建 | 大量占用资源但不一定用到的单例 |
总结
到这里就结束啦~
谢谢大家,希望对您有所帮助~