内存泄露
分类
堆内存泄露:程序执行中依据需要,通过分配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;