概述
CRTP(Curiously Recurring Template Pattern)是一种 C++ 编程技巧,使用模板类和继承的组合来实现静态多态。该模式的关键思想是:在模板类的定义中,模板参数是当前类自身(通常是派生类)。这个技巧通常用于实现编译时多态,优化性能。翻译成代码,一般是这种形式:
// 先定义一个模板类作为基类
template <typename T>
class Base
{
...
};
// 定义一个派生类,这个类继承以自身作为参数的基类
class Derived : public Base<Derived>
{
...
};
代码分析
假设当前需要设计一个 Info 类中包含 getName() 接口,有不同的子类对该接口进行实现,很容易想到的方法是利用 C++ 的多态机制,实现大概是这样:
struct IInfo
{
virtual std::string getClassName() = 0;
};
class A : public IInfo
{
std::string getClassName() override
{
return "A";
}
};
class B : public IInfo
{
std::string getClassName() override
{
return "B";
}
};
这样是传统动态多态的实现方法,在运行时通过查询虚函数表,找到实际调用接口,返回正确的类名。在看看如果用 CRTP 的形式如何实现:
template <typename T>
class Info
{
std::string getClassName()
{
return static_cast<T *>(this)->getClassNameImpl();
}
};
class A : public Info<A>
{
std::string getClassNameImpl()
{
return "A";
}
};
class B : public Info<B>
{
std::string getClassNameImpl()
{
return "B";
}
};
这里使用了 static_cast 进行类型转换,根据 CRTP 的定义,在 Info 的派生类中调用 getClassName 接口,并且 T 就是这里的派生类,这里的 static_cast 转换一定是合法的,因为这里的 this 就是派生类型 T。
代码测试
测试代码:
int main()
{
IInfo *pA = new A();
IInfo *pB = new B();
std::cout << pA->getClassName() << std::endl;
std::cout << pB->getClassName() << std::endl;
C classC;
D classD;
std::cout << classC.getClassName() << std::endl;
std::cout << classD.getClassName() << std::endl;
return 0;
}
测试结果如图,动态多态和静态多态都能够实现输出正确的类名。
对比
特性 | CRTP(静态多态) | 动态多态 |
---|---|---|
性能 | 高效,无运行时开销 | 有虚函数表查找开销,性能略低 |
灵活性 | 受限,类型在编译时固定 | 灵活,类型可以在运行时动态选择 |
类型安全性 | 高,编译时检查 | 低,存在类型转换失败风险 |
编译期 vs 运行期 | 完全在编译时 | 依赖运行时 |
耦合性 | 较高,A 模块使用 B 模块中的 CRTP 实现,涉及到的符号都得对 A 模块可见 | 较低,A 模块使用 B 模块中的接口类,接口实际实现的类不需要对 A 模块暴露 |
可读性 | 很差,涉及到模版,还存在代码体积膨胀问题 | 较差 |
关注公众号:C++学习与探索,有惊喜哦~