C++中使用函数作为回调时如何避免额外存储开销?
解决方案
C++20及以上版本可通过非类型模板参数(NTTP)+ 自定义CTAD推导指引实现需求,无需使用宏,无需用户修改现有调用写法,同时还能保留运行期函数指针传入的合法性。
修改后的库代码实现
// ***** 库代码 ***** #include <concepts> #include <utility> // 辅助空类型:将编译期函数地址作为模板参数存储,实例无任何存储开销 template<auto Func> struct function_constant { constexpr auto operator()(auto&&... args) const noexcept(noexcept(Func(std::forward<decltype(args)>(args)...))) -> decltype(Func(std::forward<decltype(args)>(args)...)) { return Func(std::forward<decltype(args)>(args)...); } }; template <std::invocable CbT> struct delegated { explicit constexpr delegated(CbT cb) : cb_(std::move(cb)) {} private: [[no_unique_address]] CbT cb_; }; // 通用CTAD推导指引:匹配所有编译期可知的函数指针,自动包装为function_constant template<class R, class... Args, R(*Func)(Args...)> delegated(R(*)(Args...)) -> delegated<function_constant<Func>>;
运行效果
原有用户代码无需任何修改,重新编译后输出如下:
func: 1 <----- 已消除存储开销,符合预期 func_ptr: 8 <----- 运行期函数指针保持原有逻辑,结果合理 lambda: 1 <----- 无捕获lambda效果不变 functor: 1 <----- 自定义仿函数效果不变
实现原理
- 当用户调用
delegated{func}时,函数名func退化为编译期可知的函数指针常量,触发自定义CTAD推导指引,自动将函数地址封装为function_constant空类型,实例无需存储任何数据,因此sizeof为1。 - 当用户传入运行期可变的函数指针变量时,由于其值无法在编译期确定,不会匹配到上述推导指引,会走原有通用模板逻辑存储指针,完全符合设计预期。
- 无捕获lambda、自定义仿函数等原有调用逻辑完全不受影响,无兼容性问题。
如果需要兼容C17及更早版本,才需要借助宏来自动生成包装lambda的代码,只要目标编译环境支持C20,上述方案即可完全满足需求。
内容的提问来源于stack exchange,提问作者user4442671
相关产品推荐
相关产品推荐

