析构函数被删除的类的特性及实际开发用途问询
Great question! 析构函数被标记为= delete的类看起来有点反常识,但在实际开发中确实有不少实用的场景,我来给你梳理几个最常见的:
绑定对象生命周期到整个进程
有些对象我们希望它从程序启动到退出一直存在,绝对不能被意外销毁。比如全局服务类、核心配置管理类这类基础组件,把析构函数删除后:- 不能在栈上创建实例(栈对象会在作用域结束时自动调用析构,编译器直接报错)
- 堆上创建的实例无法用
delete销毁,只能一直存在到进程结束,由操作系统自动回收内存
典型的例子就是这种“永不销毁”的单例:
class CoreConfig { public: CoreConfig() { /* 加载全局配置文件 */ } ~CoreConfig() = delete; // 禁止销毁 static CoreConfig& getInstance() { static CoreConfig* instance = new CoreConfig(); return *instance; } // 提供配置访问接口 std::string getConfig(const std::string& key) { return m_configMap[key]; } private: std::unordered_map<std::string, std::string> m_configMap; };这样就能保证核心配置从程序启动就加载完成,全程可用,不会被误销毁。
管理进程级全局资源
对于共享内存、全局硬件句柄、进程级锁这类资源,它们的释放逻辑应该和进程生命周期绑定,而不是由某个对象的析构来触发。把析构函数删除,就能强制开发者不能通过销毁对象来释放这些资源,避免出现资源提前释放、重复释放的问题。
比如封装系统共享内存的类:class ProcessSharedMemory { public: ProcessSharedMemory(const std::string& name, size_t size) { // 创建/挂载共享内存 m_shmemFd = shm_open(name.c_str(), O_CREAT | O_RDWR, 0666); ftruncate(m_shmemFd, size); m_memoryPtr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, m_shmemFd, 0); } ~ProcessSharedMemory() = delete; // 禁止通过析构释放资源 void* getMemory() const { return m_memoryPtr; } // 提供静态方法,在进程退出时统一清理 static void cleanupAll() { // 这里处理所有共享内存的释放逻辑 } private: int m_shmemFd; void* m_memoryPtr; };开发者只能通过类提供的全局清理方法(或者注册进程退出钩子)来释放资源,不会误操作。
强制使用堆存储大型对象
有些对象体积极大(比如数GB的缓存池、大尺寸的机器学习模型),如果在栈上创建会直接导致栈溢出。把析构函数删除后,编译器会禁止在栈上定义该类的变量(因为栈对象必须能自动调用析构),只能用new在堆上分配,从语法层面强制开发者规避栈溢出风险。RAII的特殊变种:只初始化,不负责清理
常规RAII类是“构造获取资源,析构释放资源”,但有些场景下,资源的释放由外部系统负责(比如容器编排系统管理的外部资源、由操作系统内核回收的句柄)。这时候可以把析构函数删除,让类只负责资源的初始化和访问,避免析构时误释放不属于它管理的资源。
注意事项
这类对象的内存会由操作系统在进程退出时自动回收,所以不会造成传统意义上的内存泄漏(进程退出后所有内存都会被回收)。但对于非内存资源(如文件句柄、共享内存),一定要确保有对应的全局清理逻辑,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Gamer.Godot

