CRTP(Curious Recurring Template Pattern)
CRTP是什么?
一种C++模板编程技术
cpp
template<typename Derived>
class Base {
}
// 派生类以身试法,打入基类内部
class Derived: public Base<Derived> {
}
之所以称为Curious, 是因为派生类这个还没完全定义好的类型,被用作基类的模板参数。
要解决什么问题?
主要目的是实现编译期多态(静态多态),相应的有运行期多态(动态多态)。二者对比如下:
| 特性 | 动态多态 | 静态多态 |
|---|---|---|
| 机制 | 虚函数表,运行时查找 | 模板实例化,编译期确定 |
| 开销 | 间接函数调用,多一次查表,有运行时开销 | 无需函数开销,函数调用可以被内联 |
| 灵活性 | 高,运行时绑定,非常灵活 | 低,编译期绑定,类型关系必须确定 |
| 典型用途 | 需要运行时灵活替换行为的场景 | 需要极致性能,或为相关类提供统一接口/功能 |
通过以下代码对比以下动态多态和静态多态的区别:
-
动态多态:
cppclass Animal { public: virtual void speak() const = 0; }; class Dog: public Animal { public: void speak() const override { std::cout << "Woof!\n"; } }; class Cat: public Animal { public: void speak() const override { std::cout << "Meow!\n"; } }; void talk(const Animal& a) { a.speek(); } int main() { Dog d; Cat c; talk(d); talk(c); return 0; } -
CRTP静态多态
cpptemplate<typename Derived> class Animal{ public: void speak() const { static_cast<const Derived*>(this)->speakImpl(); } }; class Dog: public Animal<Dog> { public: void speakImpl() const { std::cout << "Woof!\n"; } }; class Cat: public Animal<Cat> { public: void speakImpl() const { std::cout << "Meow!\n"; } }; template<typename T> void talk(const Animal<T>& a) { a.speak(); } int main() { Dog d; Cat c; talk(d); talk(c); return 0; }
典型的应用场景
-
静态多态
cpp#include <iostream> #include <vector> template <typename T> class Shape { public: void draw() const { static_cast<const T*>(this)->drawImpl(); } double area() const { return static_cast<const T*>(this)->areaImpl(); } void printInfo() const { std::cout << "Shape type:" << T::getName() << "Area:" << area() << std::endl; } }; class Circle: public Shape<Circle> { private: double radius_; public: Circle(double r) : radius_(r) {} void drawImpl() const { std::cout << "Drawing a circle with radius " << radius_ << std::endl; } double areaImpl() const { return 3.1415 * radius_ * radius_; } static const char* getName() { return "Circle"; } }; class Rectangle: public Shape<Rectangle> { private: double width_; double height_; public: Rectangle(double w, double h) : width_(w), height_(h) {} void drawImpl() const { std::cout << "Drawing a rectangle with width " << width_ << " and height " << height_ << std::endl; } double areaImpl() const { return width_ * height_; } static const char* getName() { return "Rectangle"; } }; template<typename T> void processShape(const T& shape) { shape.draw(); shape.printInfo(); } int main() { Circle circle(5.0); Rectangle rect(4.0, 5.0); processShape(circle); processShape(rect); return 0; } -
对象计数
cpp#include <iostream> template<typename T> class ObjectCounter { protected: ObjectCounter() {++count;} ~ObjectCounter() {--count;} public: static int getCount() {return count;} private: static int count; }; template<typename T> int ObjectCounter<T>::count = 0; class Apple: public ObjectCounter<Apple> {}; class Banana: public ObjectCounter<Banana> {}; int main() { Apple a1, a2; Banana b1; std::cout << "Apple instance: " << Apple::getCount() << std::endl; // 2 std::cout << "Banana instance: " << Banana::getCount() << std::endl; // 1 { Banana b2; std::cout << "Banana instance: " << Banana::getCount() << std::endl; // 2 } std::cout << "Banana instance: " << Banana::getCount() << std::endl; // 1 return 0; } -
链式调用(Fluent Interface)
cpp#include <iostream> #include <string> template<typename Derived> class Builder { protected: Derived& self() { return static_cast<Derived&>(*this); } public: Derived& setColor(const std::string& color) { self().color = color; return self(); } Derived& setSize(int size) { self().size = size; return self(); } Derived& setTransparent(bool transparent) { self().transparent = transparent; return self(); } }; class WindowBuilder : public Builder<WindowBuilder> { private: std::string color = "white"; int size = 100; bool transparent = false; // 让基类能够访问私有成员 friend class Builder<WindowBuilder>; public: WindowBuilder() = default; WindowBuilder& setTitle(const std::string& title) { this->title = title; return *this; } void build() const { std::cout << "Building window with: " << std::endl << " title: " << title << std::endl << " color: " << color << std::endl << " size: " << size << std::endl << " transparent: " << transparent << std::endl; } private: std::string title = "Untitled"; }; int main() { WindowBuilder() .setTitle("My application") .setColor("blue") .setSize(800) .setTransparent(true) .build(); return 0; } -
代码复用
先给出示例代码
示例1,为任何提供了
operator==和operator<的类型自动添加其他的比较运算符:一般情况下,如果你想为多个类都提供多个比较运算符,每个类都要实现一遍所有的运算符重载逻辑,代码非常冗余。
使用CRTP可以达到一种"能力注入"的效果,像下面的代码,
Value这个类只需要实现operator==和operator<,然后通过继承EqualityComparable和LessThanComparable就将其他的所有比较运算符都注入到了Value这个类中。cpp#include <iostream> #include <concepts> template <typename Derived> class EqualityComparable { public: bool operator!=(const Derived &other) const { return !(static_cast<const Derived &>(*this) == other); } }; template <typename Derived> class LessThanComparable { public: bool operator>(const Derived &other) const { return other < static_cast<const Derived&>(*this); } bool operator<=(const Derived &other) const { return !(other < static_cast<const Derived&>(*this)); } bool operator>=(const Derived &other) const { return !(static_cast<const Derived&>(*this) < other); } }; // Value类提供了operator==运算符,通过CRTP获得了operator!=运算符。这样对所有想添加 // operator!=的类,只需要继承EqualityComparable即可,无需每个类重载operator!= class Value : public EqualityComparable<Value>, public LessThanComparable<Value> { public: Value(int val) : value{val} {} bool operator==(const Value &other) const { return value == other.value; } bool operator<(const Value &other) const { return value < other.value; } private: int value; }; // Value自动拥有了operator!= int main() { Value a{1}, b{2}; std::cout << std::boolalpha; std::cout << "a < b: " << (a < b) << std::endl; std::cout << "a > b: " << (a > b) << std::endl; std::cout << "a <= b: " << (a <= b) << std::endl; std::cout << "a >= b: " << (a >= b) << std::endl; std::cout << "a != b: " << (a != b) << std::endl; return 0; }示例2,使用CRTP实现一个通用的clone接口,每个类都可以被克隆,而无需在每个类中重复实现clone代码。
cpptemplate<typename Derived> class Cloneable { public: virtual std::unique_ptr<Derived> clone() const { return std::make_unique<Derived>(static_cast<const Dervied&>(*this)); } virtual ~Cloneable() = default; }; class Shape : public Cloneable<Shape> { public: virtual void draw() const { std::cout << "Drawing a shape\n"; } } class Circle : public Cloneable<Circle> { public: Circle(int r) : radius(r) {} void draw() const { std::cout << "Drawing a circle, radius=" << radius << std::endl; } private: int radius; } int main() { Circle c{5}; auto c2 = c.clone(); c2->draw(); Shape s; auto s2 = s.clone(); s2->draw(); return 0; } -
单例模式
先给出示例代码
cpp#include <iostream> template<typename T> class Singleton { protected: Singleton() = default; ~Singleton() = default; Singleton(const Singleton& other) = delete; Singleton& operator=(const Singleton& other) = delete; Singleton(Singleton&& other) = delete; Singleton& operator=(Singleton&& other) = delete; public: static T& getInstance() { static T instance; return instance; } }; class Logger: public Singleton<Logger> { // 让基类能构造logger friend class Singleton<Logger>; private: Logger() { std::cout << "Logger initialized" << std::endl; } public: void log(const std::string& message) { std::cout << "Log: " << message << std::endl; } void error(const std::string& message) { std::cout << "Error: " << message << std::endl; } }; class ConfigManager: public Singleton<ConfigManager>{ friend class Singleton<ConfigManager>; private: ConfigManager() { std::cout << "ConfigManager initialized" << std::endl; } std::string configValue = "default"; public: const std::string& getConfig() const { return configValue; } void setConfig(const std::string& value) { configValue = value; } }; int main() { Logger::getInstance().log("This is a log message"); std::cout << "Config: " << ConfigManager::getInstance().getConfig() << std::endl; Logger& l1 = Logger::getInstance(); Logger& l2 = Logger::getInstance(); std::cout << "same logger instance? " << (&l1 == &l2) << std::endl; return 0; }单例模式的核心在于控制实例化的过程。
Singleton的构造函数为protected,因为CRTP基类需要允许派生类继承但要阻止外部直接实例化;- 派生类的构造函数为
private,阻止派生类直接实例化; - 基类需要调用派生类的构造函数,但派生类的构造函数是
private,所以必须声明friend允许基类访问。
可见,使用CRTP进行单例控制的原理的精妙之处在于权限的组合控制:
使用CRTP实现单例模式的好处:
- 代码量骤减:它把一些"体力活"全部封装到了一个模板基类中,比如声明静态方法,私有化构造函数,禁用拷贝构造函数、赋值运算、移动构造构造函数等,这些操作都在基类中做了,不需要具体类重复做这些事了。
- 强制规范:基类直接删除了拷贝构造函数,意味着任何继承了Singleton的类,自动获得了不可拷贝的特性,这比口头叮嘱程序员"不要拷贝这个类"要有效的多
- 类型安全:由于使用了
CRTP,getInstance()返回的直接就是一个具体的类型,不是一个模糊的void*或Base*。不需要在进行类型转化。