You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从已删除析构函数的类派生CRTP计数器类遇报错求解

解决方案

要实现满足需求的CRTP计数器类,我们需要同时约束对象的创建方式(仅全局/静态)、禁止复制、限制析构时机(仅程序退出时),以下是具体实现:

核心思路

  1. 禁止非全局对象创建:通过编译期断言+运行时检查,确保对象只能在全局/静态作用域初始化。
  2. 禁止动态对象:删除派生类的operator new和operator delete,阻止new创建实例。
  3. 禁止复制:显式删除拷贝构造函数和赋值运算符。
  4. 限制析构时机:通过std::atexit标记程序退出阶段,析构函数仅允许在此阶段执行。
  5. 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;
}

代码说明

  1. 编译期约束:基类构造函数中的static_assert(std::is_constant_evaluated())会在非全局初始化时触发编译错误,直接禁止局部对象定义。
  2. 动态对象拦截:派生类删除operator new和operator delete,彻底阻止new创建动态实例。
  3. 析构时机控制:通过std::atexit注册的回调会在程序退出前标记is_exiting为true,析构函数检查该标记,非退出阶段的析构会直接终止程序。
  4. 计数器逻辑:CRTP基类维护instance_count,派生类构造时递增、析构时递减,自动统计全局实例数量。

对初始错误的解释

你最初的代码报错是因为基类析构函数被显式删除,导致派生类的析构函数被隐式删除,而全局对象在程序退出时需要调用析构函数,因此触发了“使用已删除函数”的错误。解决方案是保留基类析构函数的正常访问权限,转而通过运行时检查限制析构时机。

内容的提问来源于stack exchange,提问作者Nathan29006781

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 20:20:41