C++编译期constexpr注册外设派生类回调运行期调用方案问询
实现方案
原代码问题
static constexpr修饰的成员默认是只读常量,无法在构造函数中执行赋值、自增等修改操作- 派生类使用私有继承,导致基类指针无法指向派生类实例,注册逻辑本身不成立
可行实现(基于C++20)
C++20扩展了constexpr的能力,支持编译期修改可变常量、调用虚函数,可以满足你的需求,完整实现代码如下:
#include <stdint.h> #include <array> #include <iostream> class PeripheralBase { public: // constexpr构造函数,编译期执行注册 constexpr PeripheralBase() { if (count_cb_ < registered_cb_.size()) { registered_cb_[count_cb_++] = this; } } virtual constexpr void Callback() = 0; static void RunCallback() { for (auto i = 0; i < count_cb_; i++) { registered_cb_[i]->Callback(); } } // C++20支持inline constexpr静态成员为可变状态 static inline constexpr std::size_t max_peripheral_num = 5; static inline constexpr std::array<PeripheralBase*, max_peripheral_num> registered_cb_ = []{ std::array<PeripheralBase*, max_peripheral_num> arr{}; return arr; }(); static inline constexpr int count_cb_ = 0; }; // 改为public继承 class Adc : public PeripheralBase { public: constexpr Adc() = default; void Callback() override { std::cout << "AdcCallback\n"; } }; class Timer : public PeripheralBase { public: constexpr Timer() = default; void Callback() override { std::cout << "TimerCallback\n"; } }; // 编译期构造实例,自动完成注册 constexpr Timer tim1; constexpr Adc adc; int main () { while(1) { PeripheralBase::RunCallback(); // 测试用,防止死循环可打开下行注释 // break; } return 0; }
关键修改说明
- 将注册数组和计数器修改为
static inline constexpr类型,C++20标准下支持在constexpr上下文(比如constexpr构造函数)中修改这类可变常量的状态 - 派生类改为public继承,确保基类指针可以正常指向派生类实例
- 回调函数改为纯虚函数,强制派生类实现对应逻辑
- 注册逻辑增加边界判断,避免数组越界
编译要求
需要使用支持C++20及以上标准的编译器编译,测试验证可在GCC 10+、Clang 13+、MSVC 2022+版本正常运行。
兼容低版本C的替代方案(无需C20)
如果无法使用C++20标准,可以借助编译器的自定义段特性实现编译期注册,以GCC为例:
- 在基类中定义自定义段,将每个派生类实例的指针放到指定段中
- 链接时链接器会自动把所有注册的指针放到连续的内存空间
- 运行期遍历段的起止地址即可获取所有注册的实例指针
这种方案不需要修改C++标准版本,适合嵌入式、内核等低版本编译器场景。
内容的提问来源于stack exchange,提问作者Tobias
相关产品推荐
相关产品推荐

