单例模式

tech2026-08-21  0

单例模式

1 什么是单例模式?2 实现单例的步骤3 单例对象释放问题4 单例模式C++实现

建议阅读本文之前,可以先看以下几篇博客,本文仅是我个人记录: 1、单例设计模式 2、C++ 单例模式讲解和代码示例 3、使用 Valgrind 检测 C++ 内存泄漏


1 什么是单例模式?

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

常见的场景:如系统日志、Windows资源管理器窗口、数据库分配主键操作等。(工具窗口的创建)

2 实现单例的步骤

1)构造函数私有化 2)增加静态私有的当前类的指针变量 3)提供静态对外接口,可以让用户获得单例对象

单例模式,分为懒汉式、饿汉式

懒汉式 ——指系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。(这种方式要考虑线程安全)

饿汉式 ——指系统一运行,就初始化创建实例,当需要时,直接调用即可。(本身就线程安全,没有多线程的问题)

懒汉式和饿汉式的区别在于:(创建实例的时间不同) • 懒汉式是调用getInstance()时,才会创建对象; • 饿汉式是定义静态成员变量时,创建单例对象。

3 单例对象释放问题

一般情况下,我们不需要去释放单例对象,因为单例对象只会创建一个,占不了多大的空间,static变量会在程序结束时自动释放,因此,可以不去释放单例对象。

如果一定要自己去释放,可以在单例类中添加下面的类,这样就可以释放了。

进程结束时,静态对象的生命周期随之结束,其析构函数会被调用来释放对象。因此,我们可以利用这一特性,在单例类中声明一个内嵌类,该类的析构函数专门用来释放new出来的单例对象,并声明一个该类类型的static对象。

通过内存检测工具检查单例对象是否释放?

未将内存释放时: 从下面的检测结果中可以看到total heap usage: 2 allocs, 0 frees, 72,736 bytes allocated,表示分配了2次,释放了0次,显然是单例对象没有释放。

【这里检测结果看不明白的可以去了解一下内存检测工具Valgrind,使用教程可见>>>>使用 Valgrind 检测 C++ 内存泄漏】

==28372== Memcheck, a memory error detector ==28372== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==28372== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==28372== Command: ./main_cpp ==28372== ==28372== ==28372== HEAP SUMMARY: ==28372== in use at exit: 72,736 bytes in 2 blocks ==28372== total heap usage: 2 allocs, 0 frees, 72,736 bytes allocated ==28372== ==28372== LEAK SUMMARY: ==28372== definitely lost: 0 bytes in 0 blocks ==28372== indirectly lost: 0 bytes in 0 blocks ==28372== possibly lost: 0 bytes in 0 blocks ==28372== still reachable: 72,736 bytes in 2 blocks ==28372== suppressed: 0 bytes in 0 blocks ==28372== Reachable blocks (those to which a pointer was found) are not shown. ==28372== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==28372== ==28372== For counts of detected and suppressed errors, rerun with: -v ==28372== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

使用释放类进行释放时: 可以看到,释放了1次,那么为什么still reachable: 72,704 bytes in 1 blocks还有内存没有释放呢? 这里主要是C++内存池的原因,不是单例对象没有释放。

==28781== Memcheck, a memory error detector ==28781== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==28781== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==28781== Command: ./main_cpp ==28781== ==28781== ==28781== HEAP SUMMARY: ==28781== in use at exit: 72,704 bytes in 1 blocks ==28781== total heap usage: 2 allocs, 1 frees, 72,736 bytes allocated ==28781== ==28781== LEAK SUMMARY: ==28781== definitely lost: 0 bytes in 0 blocks ==28781== indirectly lost: 0 bytes in 0 blocks ==28781== possibly lost: 0 bytes in 0 blocks ==28781== still reachable: 72,704 bytes in 1 blocks ==28781== suppressed: 0 bytes in 0 blocks ==28781== Reachable blocks (those to which a pointer was found) are not shown. ==28781== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==28781== ==28781== For counts of detected and suppressed errors, rerun with: -v ==28781== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
4 单例模式C++实现

下面的实现包含单例模式的两种:懒汉式和饿汉式。

#include <crtdbg.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <vector> #include <thread> #include <mutex> #include <string> using namespace std; //懒汉式 #if 1 class Singleton { public: static Singleton* getInstance(const std::string &value) { if (_singleton == nullptr) { std::lock_guard<std::mutex> lock(_mutex); if(_singleton==nullptr) //使用双重锁定 _singleton = new Singleton(value); } return _singleton; } std::string value() const { return _value; } //Singletons should not be cloneable. Singleton(Singleton &other) = delete; //Singletons should not be assignable. void operator=(const Singleton &) = delete; private: Singleton(const std::string value):_value(value) {} //~Singleton(){} static Singleton* _singleton; class GarbageCollector {//单例释放 public: ~GarbageCollector() { if (_singleton!=nullptr) { delete _singleton; _singleton = nullptr; } } }; static GarbageCollector gc; static std::mutex _mutex; std::string _value; }; Singleton* Singleton::_singleton = nullptr; Singleton::GarbageCollector Singleton::gc; std::mutex Singleton::_mutex; void ThreadFoo() { this_thread::sleep_for(std::chrono::milliseconds(1000)); Singleton* singleton = Singleton::getInstance("Foo"); cout << singleton->value() << endl; } void ThreadBar() { this_thread::sleep_for(std::chrono::milliseconds(1000)); Singleton* singleton = Singleton::getInstance("Bar"); cout << singleton->value() << endl; } #endif //饿汉式 #if 0 class Singleton { public: static Singleton* getInstance() { return _singleton; } std::string value() const { return _value; } //Singletons should not be cloneable. Singleton(Singleton &other) = delete; //Singletons should not be assignable. void operator=(const Singleton &) = delete; private: Singleton(const std::string value) :_value(value) {} ~Singleton() {} static Singleton* _singleton; std::string _value; }; Singleton* Singleton::_singleton = new Singleton("Fisrt"); void ThreadFoo() { this_thread::sleep_for(std::chrono::milliseconds(500)); Singleton* singleton = Singleton::getInstance(); cout << singleton->value() << endl; } void ThreadBar() { this_thread::sleep_for(std::chrono::milliseconds(500)); Singleton* singleton = Singleton::getInstance(); cout << singleton->value() << endl; } #endif #if 1 int main(int argc, char** argv) { thread t1(ThreadFoo); thread t2(ThreadBar); t1.join(); t2.join(); system("pause"); return 0; } #endif
最新回复(0)