如何采用CRTP模式重写含虚函数的C++处理代码?
问题描述
原本的静态硬编码测试代码改成动态绑定实现后,发现虚调用的速度慢了约一倍。原代码如下:
#include <vector> class vUnit { public: virtual ~vUnit() {;} virtual float process(float f) = 0; }; class vRack { public: vRack() {;} ~vRack() {;} inline void addUnit(vUnit* unit_ptr) { _units.push_back(unit_ptr); } inline float process_all(float f) { float res = 0.; for (int j = 0 ; j< 60; j++) { for (auto u : _units) { f = u->process(f); } } res += f; return res; } private: std::vector<vUnit*> _units ; int _i = 0; }; class vMultiplyF : public vUnit { public: vMultiplyF(float v) {_f = v;} inline float process(float f) final { return f * _f; } private: float _f; }; class vDivideF : public vUnit { public: vDivideF(float v) {_f = v;} inline float process(float f) final { if (f > 0) { return _f / f; } else return 0.0f; } private: float _f; }; int main () { float f = 1.5f, r = 0.0f; vRack vR; vMultiplyF vM(5.1f); vDivideF vD(5.5f); vR.addUnit(&vM); vR.addUnit(&vD); for( int i = 0 ; i < 1000 ; i++) { f = vR.process_all(f); r += f; } return 0; }
我认为CRTP是合理的折中方案,但不清楚如何改写vRack.addUnit()方法,使其能够接受不同的vUnit实现。
基于CRTP的解决方案
CRTP(奇异递归模板模式)可通过静态多态消除虚函数的运行时开销,同时结合类型擦除让vRack支持存储不同类型的CRTP派生类,具体改写步骤如下:
1. 定义CRTP基类
替换原有的虚基类vUnit,实现静态多态的基础框架:
template <typename Derived> class UnitCRTP { public: float process(float f) { // 静态绑定调用派生类的具体实现,无虚函数开销 return static_cast<Derived*>(this)->process_impl(f); } };
2. 改写原有Unit类
让vMultiplyF和vDivideF继承UnitCRTP,并实现process_impl方法(无需再写虚函数):
class vMultiplyF : public UnitCRTP<vMultiplyF> { public: vMultiplyF(float v) : _f(v) {} float process_impl(float f) { return f * _f; } private: float _f; }; class vDivideF : public UnitCRTP<vDivideF> { public: vDivideF(float v) : _f(v) {} float process_impl(float f) { if (f > 0) { return _f / f; } else return 0.0f; } private: float _f; };
3. 改造vRack实现类型擦除
通过std::function实现类型擦除,让vRack能存储任意符合签名的可调用对象:
#include <vector> #include <functional> class vRack { public: vRack() = default; ~vRack() = default; // 模板化addUnit,接受任意CRTP派生类实例 template <typename UnitType> void addUnit(UnitType& unit) { // 将unit的process方法包装为统一签名的函数对象 _units.emplace_back([&unit](float f) { return unit.process(f); }); } float process_all(float f) { float res = 0.; for (int j = 0; j < 60; j++) { for (auto& func : _units) { f = func(f); } } res += f; return res; } private: // 存储统一签名的函数对象,替代原有的基类指针容器 std::vector<std::function<float(float)>> _units; };
4. 主函数直接复用
原main函数无需修改即可正常运行,addUnit现在能自动适配不同类型的CRTP派生类:
int main () { float f = 1.5f, r = 0.0f; vRack vR; vMultiplyF vM(5.1f); vDivideF vD(5.5f); vR.addUnit(vM); vR.addUnit(vD); for( int i = 0 ; i < 1000 ; i++) { f = vR.process_all(f); r += f; } return 0; }
方案优势
- CRTP通过静态绑定让编译器可对
process_impl做内联优化,性能接近静态硬编码; std::function的类型擦除保留了动态添加不同类型Unit的灵活性;- 完全规避了虚函数表查找的运行时开销。
内容的提问来源于stack exchange,提问作者Copych
相关产品推荐
相关产品推荐

