文章目录
- 前言
- 一、 设计一个不能被拷贝的类
- 1. C++98 实现方式
- 2. C++11 实现方式
- 二、设计一个只能在堆上创建对象的类
- 1. 方法一:析构函数私有,提供destory接口释放资源
- 2. 方法二:构造函数私有
- 三、 设计一个只能在栈上创建对象的类
- 1. 实现方式
- 四、设计一个不能被继承的类
- 1. C++98 实现方式
- 2. C++11 实现方式
- 五、设计一个只能创建一个对象(单例模式)
- 1. 单例模式介绍
- 2. 饿汉模式(Eager Singleton)
- (1)饿汉模式的实现步骤
- (2)代码解析
- (3)饿汉模式的优缺点
- 3. 懒汉模式(Lazy Singleton)
- (1)为什么使用懒汉模式?
- (2)代码解析
- (4)防止拷贝
- (5)对象持久化
- 4. 饿汉VS懒汉
- 总结
前言
今天我们一起来学习常见的特殊类怎么设计,以及了解什么是单例~
一、 设计一个不能被拷贝的类
拷贝发生在两种场景:拷贝构造函数和赋值运算符。因此,若要禁止拷贝,只需让该类不能调用这两个函数。
1. C++98 实现方式
- 方法:将拷贝构造函数和赋值运算符 仅声明不定义,并设置为
private
。 - 原因:
- 私有化拷贝构造和赋值运算符,防止外部访问。
- 仅声明不定义,确保该函数不会被调用,即使成员函数内部尝试拷贝也会报错。
class CopyBan {
private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);
};
2. C++11 实现方式
- C++11 提供
= delete
语法,显式删除默认拷贝构造和赋值运算符。
class CopyBan {
public:CopyBan(const CopyBan&) = delete;CopyBan& operator=(const CopyBan&) = delete;
};
二、设计一个只能在堆上创建对象的类
1. 方法一:析构函数私有,提供destory接口释放资源
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. 方法二:构造函数私有
步骤:
- 构造函数私有
- 提供静态成员函数创建对象
- 禁拷贝与赋值,防止利用拷贝创建栈上的对象
具体代码和使用如下:
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
具体代码如下:
class StackOnly
{
public:static StackOnly CreateObj(){StackOnly st;return st;}
private:StackOnly(){//...}// 对一个类实现专属operator newvoid* 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
,防止继承。
class NonInherit {
public:static NonInherit GetInstance() {return NonInherit();}private:NonInherit() {}
};
2. C++11 实现方式
- 方法:使用
final
关键字。
class A final {// 该类无法被继承
};
五、设计一个只能创建一个对象(单例模式)
1. 单例模式介绍
- 保证系统中某个类只有一个实例。
- 提供全局访问点。
- 应用场景:如全局配置管理。
2. 饿汉模式(Eager Singleton)
- 特点:程序启动时即创建实例。(main函数之前就创建)
- 优点:线程安全。
- 缺点:可能导致启动慢。
(1)饿汉模式的实现步骤
代码:
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)构造函数私有化
Singleton() {}
- 目的是防止外部代码通过
new
关键字创建对象,确保Singleton
类只能在GetInstance()
方法中创建实例。
(2)提供静态方法 GetInstance()
static Singleton& GetInstance()
{return _sinst;
}
- 通过静态方法返回单例对象的引用,确保所有地方访问的都是同一个实例。
(3)防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
- 防止拷贝和赋值,避免创建多个
Singleton
实例。
(4)静态成员变量 _sinst
static Singleton _sinst;
- 程序启动时(
main()
执行前)就创建该实例,无论是否真的需要。
(3)饿汉模式的优缺点
✅ 优点
-
线程安全
- 静态成员
_sinst
在编译时创建,天然是线程安全的,无需额外同步措施(如mutex
)。
- 静态成员
-
实现简单
- 不需要加锁,避免了懒汉模式的
double-check
加锁复杂性。
- 不需要加锁,避免了懒汉模式的
-
访问速度快
- 由于实例在程序启动时就已经创建,访问时无延迟,直接返回。
❌ 缺点
-
浪费资源
- 如果该单例对象初始化内容很多,而程序运行期间根本没用到,就会浪费资源,降低程序启动速度。
-
难以控制对象创建顺序
- 如果多个单例对象存在依赖关系(如 A 依赖 B),可能会导致未定义行为。
- 例如:
class 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)代码解析
完整代码:
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(垃圾回收)类,在程序结束时自动释放 Singletonclass 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)
static Singleton& GetInstance()
{if (_psinst == nullptr){_psinst = new Singleton;}return *_psinst;
}
特点:
- _psinst 指针初始化为
nullptr
,意味着程序启动时不会创建实例。 - 首次调用
GetInstance()
时才创建Singleton
实例。 - 之后每次调用
GetInstance()
,返回的都是同一个对象。
(2)手动释放单例
一般来说,单例是不需要去释放的,
特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)(GC)
static void DelInstance()
{if (_psinst){delete _psinst;_psinst = nullptr;}
}
作用:
- 由于
Singleton
对象是动态创建的,所以需要手动释放。 DelInstance()
用于手动释放对象,当程序需要手动控制资源释放时可以调用。
(3)GC 机制(自动释放单例)
class GC
{
public:~GC(){lazy::Singleton::DelInstance();}
};
工作原理:
GC
是Singleton
内部的一个嵌套类。static GC _gc;
是一个 静态成员变量,它的 析构函数会在程序退出时被调用,从而自动释放Singleton
实例。
⚠ 为什么要加 GC
?
- 避免 内存泄漏,因为
Singleton
对象是new
出来的,程序退出时如果不手动delete
,就会发生泄漏。 - 确保
Singleton
在main()
结束时释放,不会影响其他对象析构的顺序。
(4)防止拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
- 防止 拷贝构造 和 赋值运算符,保证单例模式不被破坏。
(5)对象持久化
~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懒汉
方式 | 线程安全 | 访问速度 | 资源消耗 | 适用场景 |
---|---|---|---|---|
饿汉模式 | ✅ 安全 | ✅ 快 | ❌ 可能浪费 | 频繁使用的单例对象(如日志、配置管理) |
懒汉模式 | ❌ 需加锁 | ❌ 访问有延迟 | ✅ 只在需要时创建 | 大量占用资源但不一定用到的单例 |
总结
到这里就结束啦~
谢谢大家,希望对您有所帮助~