【C++11】特殊类&&单例模式

内存泄露

分类
  • 堆内存泄露:程序执行中依据需要,通过分配malloc/calloc/realloc/new等从堆中分配的一块内存,用完后必须通过调用相应的free或者delete删掉,假如程序设计错误导致这部分内存没有释放,那么这部分空间无法再次被使用,就会产生Heap Leak

  • 系统资源泄露:程序使用系统分配的资源,如:套接字,文件描述符,管道等没有使用对应的函数释放掉,导致资源浪费

内存泄露检测
  • Linux下常用:valgrind,dmalloc......
避免内存泄露
  • 编码规范
  • 使用RAII思想或者智能指针来管理资源
  • 检测工具

特殊类设计

设计一个不能拷贝的类

设计一个只能在堆上创建的对象

  • 将类的构造函数私有,拷贝构造声明私有
  • 提供一个静态成员函数(解决调用函数需要对象使用),在该静态成员函数中完成堆(new)对象的创建,并返回对象的指针
  • 析构函数私有化,自定义堆上的析构函数
cpp 复制代码
class HeapOnly
{
public:
	static HeapOnly* CreatObj()
	{
		return new HeapOnly;
	}
	void DelObj()
	{
		delete this;
	}
	//HeapOnly(const HeapOnly&) = delete;   做法2
private:
	HeapOnly() {}
	HeapOnly(const HeapOnly&);		//做法1
	~HeapOnly() {}			//禁用栈上的析构函数
};

在栈上创建对象

  • 将构造函数自由化,设计静态方法创建对象返回
  • 禁掉operator new 和delete
cpp 复制代码
class StackOnly
{
public:
	static StackOnly CreatObj()
	{
		StackOnly st;
		return st;
	}
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
private:
	StackOnly() {

	}
	int _a;
};

设计一个不能继承的类

  • 构造函数私有化(子类创建时需要父类的对象)
  • 使用final修饰类,表示类不能被继承

单例模式

  • 一个类只能创建一个对象,一份配置文件,配置信息唯一。如:服务器的配置信息,IP地址,内存池
  • 下面23种单例模式

饿汉模式

  • 不管将来用不用,程序一启动就创建唯一实例对象
  • 缺点:由于静态成员初始化,进程启动缓慢
cpp 复制代码
class Singleton
{
public:
	static Singleton& GetInstance()
	{
		return _single;
	}
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	static Singleton _single;	//唯一实例,全局类型
	Singleton() {}	//构造函数私有,防止创建另一个对象
};
Singleton Singleton::_single;
单例模式核心
验证
cpp 复制代码
#include <iostream>
using namespace std;

// 全局变量:main前初始化
int global_var = []() {
    cout << "全局变量 global_var 初始化(main前)" << endl;
    return 10;
}();

// 全局静态变量:main前初始化
static int static_global_var = []() {
    cout << "全局静态变量 static_global_var 初始化(main前)" << endl;
    return 20;
}();

int main() {
    cout << "进入 main 函数" << endl;
    cout << "global_var = " << global_var << endl;
    cout << "static_global_var = " << static_global_var << endl;
    return 0;
}

cpp 复制代码
#include <iostream>
using namespace std;

void func() {
    // 局部静态变量:第一次调用func时初始化
    static int static_local_var = []() {
        cout << "局部静态变量 static_local_var 初始化" << endl;
        return 40;
    }();
    cout << "func 被调用,static_local_var = " << static_local_var << endl;
}

int main() {
    cout << "进入 main 函数" << endl;
    cout << "第一次调用 func:" << endl;
    func(); // 第一次执行到static_local_var定义,触发初始化
    cout << "第二次调用 func:" << endl;
    func(); // 已初始化,不再执行初始化逻辑
    return 0;
}

问题
  • 饿汉模式(main函数之前)就创建单例对象
  • 如果单例对象初始化内容很多,影响启动速度,启动时间长无法区分程序是否还是在启动
  • 如果两个单例类,互相有依赖关系,要求先创建A再创建B,B的初始化创建依赖A
  • 懒汉有线程安全问题---加锁,检查
解决
  • 初始化成nullptr,解决启动慢的问题
  • 只有在GetInstance 时才创建对象,解决了依赖问题,可以自定义GetInstance的顺序(main函数中调用)
cpp 复制代码
class Singleton
{
public:
	static Singleton& GetInstance()
	{
		if (_single == nullptr)
		{
			_single = new Singleton;
		}
		return *_single;
	}
	static void DelInstance()	//应用于:中途需要单例释放,程序结束需要保存数据等
	{
		if (_single)
		{
			delete _single;
			_single = nullptr;
		}
	}
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	static Singleton* _single;	//唯一实例,全局类型
	Singleton() {}	//构造函数私有,防止创建另一个对象
};
Singleton* Singleton::_single = nullptr;
提示:为什么先delete

delete会先调用析构函数释放空间,如果先把_single置空,再执行delete,就变成了delete nullptr,导致内存泄露

tips:进程结束释放资源不调用析构函数,进程结束是把页表和内存映射解开

(1)操作系统层面:强制回收内存(解开页表 / 内存映射)

  • 当进程正常退出(return 0)或异常终止(崩溃、kill 信号)时,操作系统会执行:
  • 销毁进程的页表,解除虚拟地址到物理内存的映射;
  • 回收进程占用的所有内存页(堆、栈、静态存储区);
  • 关闭进程打开的文件句柄、网络套接字等系统资源。

👉 效果:内存本身一定会被回收,不会出现 "系统级内存泄漏",但这是操作系统的 "兜底行为",和 C++ 语言层面的析构函数无关。

(2)C++ 语言层面:析构函数可能未被调用

  • 析构函数的核心作用是执行对象的自定义清理逻辑(比如:释放对象内部手动分配的堆内存(如 char* buf = new char[1024]);
  • 关闭文件 / 数据库连接、释放锁、写回缓存数据到磁盘;记录日志、通知其他模块 "对象已销毁")。

如果进程直接结束,这些自定义逻辑不会被执行------ 操作系统只认内存 / 句柄,不认 C++ 对象的析构逻辑。

懒汉模式

  • 为了解决单例对象构造耗费资源,程序启动慢,使用懒汉模式(延迟加载),即先初始化成nullptr
  • 此外需要自动的实例回收,则要有内部类,在不需要该实例时自动析构
  • 当有好几个单例的类 时,使用内部类。定义成静态成员变量,在main函数结束时自动调用_gc的析构函数,_gc属于某个单例,一定会析构
cpp 复制代码
class Singleton
{
public:
	static Singleton& GetInstance()
	{
		if (_single == nullptr)
		{
			_single = new Singleton;
		}
		return *_single;
	}
	static void DelInstance()	//应用于:中途需要单例释放,程序结束需要保存数据等
	{
		if (_single)
		{
			delete _single;
			_single = nullptr;
		}
	}

	class GC
	{
		~GC()
		{
			Singleton::DelInstance();
		}
	};

	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	static Singleton* _single;	//唯一实例,全局类型
	static GC _gc;
	Singleton() {}	//构造函数私有,防止创建另一个对象
};
Singleton* Singleton::_single = nullptr;
Singleton::GC Singleton::_gc;
相关推荐
ADDDDDD_Trouvaille1 小时前
2026.2.15——OJ83-85题
c++·算法
烟花落o1 小时前
算法的时间复杂度和空间复杂度
开发语言·数据结构·笔记·算法
西门吹-禅2 小时前
node js 性能处理
开发语言·javascript·ecmascript
我不是8神2 小时前
go-zero微服务框架总结
开发语言·微服务·golang
Ronaldinho Gaúch2 小时前
算法题中的日期问题
开发语言·c++·算法
麦德泽特2 小时前
机器人赛事系统架构:基于UDT和MQTT的低延迟、高可靠通信
c语言·开发语言·安全·系统架构·机器人
lsx2024062 小时前
TypeScript 循环
开发语言
utmhikari3 小时前
【架构艺术】治理后端稳定性的一些实战经验
java·开发语言·后端·架构·系统架构·稳定性·后端开发
csbysj20203 小时前
Swift 条件语句
开发语言