目录
[一、CRTP 是什么?](#一、CRTP 是什么?)
[三、CRTP 的典型应用](#三、CRTP 的典型应用)
[1. 静态多态:避免虚函数开销](#1. 静态多态:避免虚函数开销)
[2. 对象计数(自动统计实例数量)](#2. 对象计数(自动统计实例数量))
[3. 混入类(Mixin)------ 给现有类添加功能](#3. 混入类(Mixin)—— 给现有类添加功能)
[四、CRTP 与 C++23 的推导 this(deducing this)](#四、CRTP 与 C++23 的推导 this(deducing this))
[六、CRTP 的局限与替代方案](#六、CRTP 的局限与替代方案)
[CRTP vs 虚函数 vs std::variant](#CRTP vs 虚函数 vs std::variant)
[1. 类型转换错误(用 dynamic_cast 而不是 static_cast)](#1. 类型转换错误(用 dynamic_cast 而不是 static_cast))
[2. 忘记 const 正确性](#2. 忘记 const 正确性)
[3. 将 CRTP 用于需要运行时多态的场景](#3. 将 CRTP 用于需要运行时多态的场景)
一、CRTP 是什么?
cpp
// 基类模板:接受派生类类型作为参数
template <typename Derived>
class Base {
public:
void interface() {
// 通过 static_cast 调用派生类的实现
static_cast<Derived*>(this)->implementation();
}
};
// 派生类:把自己传给基类
class Derived : public Base<Derived> {
public:
void implementation() {
cout << "Derived 实现" << endl;
}
};
// 使用
Derived d;
d.interface(); // 输出 "Derived 实现"
"奇异递归":派生类继承自一个以自己为模板参数的基类------形成一个递归的、不寻常的模式。
cpp
class Derived : public Base<Derived> // Derived 出现在自己的基类列表中
二、为什么叫"静态多态"?
普通虚函数是动态多态(运行时决定):
cpp
class Shape {
public:
virtual void draw() = 0;
};
class Circle : public Shape {
void draw() override { cout << "Circle" << endl; }
};
Shape* s = new Circle();
s->draw(); // 运行时查 vtable 调用 Circle::draw
CRTP 是静态多态(编译期决定):
cpp
template <typename Derived>
class Shape {
public:
void draw() {
static_cast<Derived*>(this)->drawImpl();
}
};
class Circle : public Shape<Circle> {
public:
void drawImpl() { cout << "Circle" << endl; }
};
Circle c;
c.draw(); // 编译期确定调用 Circle::drawImpl
| 特性 | 动态多态(虚函数) | 静态多态(CRTP) |
|---|---|---|
| 绑定时间 | 运行时 | 编译期 |
| 调用开销 | 虚表查表(2-3 次内存访问) | 普通函数调用(可内联) |
| 代码体积 | 小(共享虚表) | 大(每个派生类生成一份基类代码) |
| 灵活性 | 高(运行时替换) | 低(编译期固定) |
| 适用场景 | 需要运行时多态 | 性能敏感、不需要运行时替换 |
三、CRTP 的典型应用
1. 静态多态:避免虚函数开销
cpp
#include <iostream>
#include <chrono>
using namespace std;
// 动态多态版本
class DynamicShape {
public:
virtual double area() const = 0;
virtual ~DynamicShape() = default;
};
class DynamicCircle : public DynamicShape {
double r;
public:
DynamicCircle(double rad) : r(rad) {}
double area() const override { return 3.14159 * r * r; }
};
// CRTP 静态多态版本
template <typename Derived>
class StaticShape {
public:
double area() const {
return static_cast<const Derived*>(this)->areaImpl();
}
};
class StaticCircle : public StaticShape<StaticCircle> {
double r;
public:
StaticCircle(double rad) : r(rad) {}
double areaImpl() const { return 3.14159 * r * r; }
};
// 使用:CRTP 版本不需要指针,可直接调用
StaticCircle c(5.0);
cout << c.area() << endl; // 编译期绑定,可内联
2. 对象计数(自动统计实例数量)
cpp
template <typename T>
class Counter {
private:
static int count;
public:
Counter() { ++count; }
Counter(const Counter&) { ++count; }
~Counter() { --count; }
static int getCount() { return count; }
};
template <typename T>
int Counter<T>::count = 0;
// 需要计数的类只需继承 Counter
class Dog : public Counter<Dog> {
string name;
public:
Dog(const string& n) : name(n) {}
};
class Cat : public Counter<Cat> {
string name;
public:
Cat(const string& n) : name(n) {}
};
int main() {
Dog d1("旺财"), d2("小黑");
Cat c1("咪咪");
cout << "Dog 数量: " << Dog::getCount() << endl; // 2
cout << "Cat 数量: " << Cat::getCount() << endl; // 1
return 0;
}
3. 混入类(Mixin)------ 给现有类添加功能
cpp
// 为类添加克隆能力
template <typename Derived>
class Cloneable {
public:
Derived clone() const {
return static_cast<const Derived&>(*this);
}
};
// 为类添加可比较能力
template <typename Derived>
class Comparable {
public:
bool operator==(const Derived& other) const {
const Derived& self = static_cast<const Derived&>(*this);
return self.equal(other);
}
bool operator!=(const Derived& other) const {
return !(*this == other);
}
};
// 组合多个 Mixin
class Point : public Cloneable<Point>, public Comparable<Point> {
int x, y;
public:
Point(int a, int b) : x(a), y(b) {}
bool equal(const Point& other) const {
return x == other.x && y == other.y;
}
};
int main() {
Point p1(1, 2), p2(1, 2), p3(3, 4);
Point p4 = p1.clone(); // 来自 Cloneable
cout << (p1 == p2) << endl; // 1,来自 Comparable
cout << (p1 == p3) << endl; // 0
return 0;
}
四、CRTP 与 C++23 的推导 this(deducing this)
C++23 引入的"推导 this"可以简化 CRTP 的写法,不再需要显式传递派生类参数:
cpp
// C++23 之前的 CRTP
template <typename Derived>
class OldBase {
void f() {
static_cast<Derived*>(this)->impl();
}
};
// C++23:用显式对象参数(deducing this)
class NewBase {
public:
template <typename Self>
void f(this Self&& self) {
self.impl();
}
};
class Derived : public NewBase {
void impl() { cout << "impl" << endl; }
};
但目前大部分代码仍使用传统 CRTP。
五、完整例子:多态容器(避免虚函数)
cpp
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
// ========== 动态多态版本 ==========
class IDynamicDrawable {
public:
virtual void draw() const = 0;
virtual ~IDynamicDrawable() = default;
};
class DynamicCircle : public IDynamicDrawable {
double r;
public:
DynamicCircle(double rad) : r(rad) {}
void draw() const override {
cout << "画圆,半径=" << r << endl;
}
};
class DynamicSquare : public IDynamicDrawable {
double side;
public:
DynamicSquare(double s) : side(s) {}
void draw() const override {
cout << "画正方形,边长=" << side << endl;
}
};
// ========== CRTP 静态多态版本 ==========
template <typename Derived>
class StaticDrawable {
public:
void draw() const {
static_cast<const Derived*>(this)->drawImpl();
}
};
class StaticCircle : public StaticDrawable<StaticCircle> {
double r;
public:
StaticCircle(double rad) : r(rad) {}
void drawImpl() const {
cout << "[CRTP] 画圆,半径=" << r << endl;
}
};
class StaticSquare : public StaticDrawable<StaticSquare> {
double side;
public:
StaticSquare(double s) : side(s) {}
void drawImpl() const {
cout << "[CRTP] 画正方形,边长=" << side << endl;
}
};
// CRTP 版本的容器需要知道具体类型(不能用基类指针统一存储)
// 解决方案:类型擦除或改用 std::variant
using StaticShape = variant<StaticCircle, StaticSquare>;
void drawAllStatic(const vector<StaticShape>& shapes) {
for (const auto& shape : shapes) {
visit([](const auto& s) { s.draw(); }, shape);
}
}
int main() {
cout << "=== 动态多态 ===" << endl;
vector<unique_ptr<IDynamicDrawable>> dynamicShapes;
dynamicShapes.push_back(make_unique<DynamicCircle>(5.0));
dynamicShapes.push_back(make_unique<DynamicSquare>(4.0));
for (const auto& s : dynamicShapes) {
s->draw();
}
cout << "\n=== CRTP 静态多态(使用 variant) ===" << endl;
vector<StaticShape> staticShapes;
staticShapes.push_back(StaticCircle(5.0));
staticShapes.push_back(StaticSquare(4.0));
drawAllStatic(staticShapes);
return 0;
}
输出:
text
=== 动态多态 ===
画圆,半径=5
画正方形,边长=4
=== CRTP 静态多态(使用 variant) ===
[CRTP] 画圆,半径=5
[CRTP] 画正方形,边长=4
六、CRTP 的局限与替代方案
| 局限 | 说明 | 替代方案 |
|---|---|---|
| 无法存储异质容器 | 不同派生类类型不同 | std::variant + 访问者模式 |
| 代码膨胀 | 每个派生类生成一份基类代码 | 虚函数更节省空间 |
| 类型关系隐藏 | 无公共基类指针 | 文档说明或概念约束 |
| 编译时间 | 增加模板实例化 | 按需使用 |
CRTP vs 虚函数 vs std::variant
| 场景 | 推荐方案 |
|---|---|
| 需要运行时多态(同一容器存不同类型) | 虚函数 |
| 性能关键且类型数量固定 | CRTP + std::variant |
| 类型数量固定且需要多种操作 | std::variant + 访问者 |
| 需要添加通用功能给多个类 | CRTP Mixin |
七、常见错误
1. 类型转换错误(用 dynamic_cast 而不是 static_cast)
cpp
// ❌ CRTP 中不应使用 dynamic_cast(基类不知道派生类,但 static_cast 足够)
static_cast<Derived*>(this); // ✅
dynamic_cast<Derived*>(this); // ❌ 多余且可能失败
2. 忘记 const 正确性
cpp
template <typename D>
class Base {
void f() const {
// 如果 D::impl 不是 const,这里需要 const_cast 或调整
static_cast<const D*>(this)->impl();
}
};
3. 将 CRTP 用于需要运行时多态的场景
如果需要在运行时替换对象,CRTP 不适用------使用传统虚函数。
八、这一篇的收获
你现在应该理解:
-
CRTP 定义 :
class Derived : public Base<Derived>,派生类把自己传给基类模板 -
静态多态:编译期绑定,无虚函数开销,可内联
-
典型应用:
-
静态多态(性能敏感场景)
-
对象计数(自动统计实例)
-
Mixin 混入类(为类添加通用功能)
-
-
与虚函数对比:CRTP 更快但缺乏运行时灵活性
-
容器存储 :CRTP 对象类型不同,需要用
std::variant或类型擦除
💡 小作业:实现一个
enable_if_streamable的 CRTP 基类,为派生类自动添加operator<<支持。要求:基类提供operator<<调用Point和Line类。
下一篇预告 :第47篇《C++代码组织:头文件、预编译指令与不透明指针(Pimpl)》------头文件应该放什么?#pragma once 是什么原理?如何减少编译依赖?Pimpl 惯用法如何隐藏实现细节?下篇讲工程实践。