从已删除析构函数的类派生CRTP计数器类遇报错求解
解决方案
要实现满足需求的CRTP计数器类,我们需要同时约束对象的创建方式(仅全局/静态)、禁止复制、限制析构时机(仅程序退出时),以下是具体实现:
核心思路
- 禁止非全局对象创建:通过编译期断言+运行时检查,确保对象只能在全局/静态作用域初始化。
- 禁止动态对象:删除派生类的
operator new和operator delete,阻止new创建实例。 - 禁止复制:显式删除拷贝构造函数和赋值运算符。
- 限制析构时机:通过
std::atexit标记程序退出阶段,析构函数仅允许在此阶段执行。 - CRTP计数器:基类维护实例计数,派生类自动继承计数逻辑。
完整代码实现
#include <iostream> #include <cstdlib> #include <type_traits> template <typename DERIVED_CLASS> class Counter { private: // 禁用拷贝与赋值 Counter(const Counter&) = delete; Counter& operator=(const Counter&) = delete; protected: static inline bool is_exiting = false; static inline std::size_t instance_count = 0; // 基类构造函数:仅派生类可访问,编译期检查是否为全局初始化 Counter() { static_assert(std::is_constant_evaluated(), "仅允许创建全局/静态实例"); ++instance_count; std::cout << "实例创建,当前计数: " << instance_count << std::endl; } // 基类析构函数:public允许全局对象正常析构 ~Counter() = default; public: // 获取当前实例数量 static std::size_t get_count() { return instance_count; } // 标记程序进入退出阶段 static void mark_exiting() { is_exiting = true; } }; // 注册退出回调:确保程序退出时标记状态 template <typename T> struct ExitMarker { ExitMarker() { std::atexit([](){ Counter<T>::mark_exiting(); }); } }; class A : public Counter<A> { private: static inline ExitMarker<A> exit_marker{}; // 自动注册退出回调 public: // 派生类构造函数:运行时二次检查初始化时机 A() { if (!std::is_constant_evaluated()) { std::cerr << "错误:仅允许创建全局/静态实例!" << std::endl; std::abort(); } } // 禁用拷贝与赋值 A(const A&) = delete; A& operator=(const A&) = delete; // 禁用动态对象创建 void* operator new(size_t) = delete; void operator delete(void*) = delete; // 析构函数:仅允许在程序退出时执行 ~A() { if (!Counter<A>::is_exiting) { std::cerr << "错误:禁止运行期间析构实例!" << std::endl; std::abort(); } --Counter<A>::instance_count; std::cout << "实例销毁,当前计数: " << Counter<A>::instance_count << std::endl; } }; // 合法:全局实例 A a; A b; int main() { // 以下操作均非法: // A c; // 编译错误:触发static_assert断言失败 // constexpr A d; // 运行时错误:析构时未进入退出阶段,程序终止 // A* p = new A; // 编译错误:operator new已被删除 // a.~A(); // 运行时错误:手动调用析构,程序终止 std::cout << "当前实例计数: " << A::get_count() << std::endl; return 0; }
代码说明
- 编译期约束:基类构造函数中的
static_assert(std::is_constant_evaluated())会在非全局初始化时触发编译错误,直接禁止局部对象定义。 - 动态对象拦截:派生类删除
operator new和operator delete,彻底阻止new创建动态实例。 - 析构时机控制:通过
std::atexit注册的回调会在程序退出前标记is_exiting为true,析构函数检查该标记,非退出阶段的析构会直接终止程序。 - 计数器逻辑:CRTP基类维护
instance_count,派生类构造时递增、析构时递减,自动统计全局实例数量。
对初始错误的解释
你最初的代码报错是因为基类析构函数被显式删除,导致派生类的析构函数被隐式删除,而全局对象在程序退出时需要调用析构函数,因此触发了“使用已删除函数”的错误。解决方案是保留基类析构函数的正常访问权限,转而通过运行时检查限制析构时机。
内容的提问来源于stack exchange,提问作者Nathan29006781
相关产品推荐
相关产品推荐

