使用 CRTP 实现混入
在编译期组合行为
使用 CRTP 实现混入 是 CoddyKit 上的免费 C++ Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C++ Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C++ Academy 课程共包含 4 节课。
什么是混入类
混入类是一种可以组合到类中的小型可复用行为单元。借助 CRTP,混入类可以在编译时添加功能,同时仍然调用宿主类中的代码。
简单的混入类
这个混入类添加了一个 print_twice 方法,该方法依赖宿主类提供 value()。
#include <iostream>
template <typename T>
struct PrintTwice {
void print_twice() const {
auto v = static_cast<const T*>(this)->value();
std::cout << v << " " << v << "\n";
}
};
struct Box : PrintTwice<Box> {
int value() const { return 7; }
};
int main() {
Box{}.print_twice();
return 0;
}组合多个混入类
您可以同时继承多个 CRTP 混入类,将彼此独立的行为叠加到一个类上。
#include <iostream>
template <typename T>
struct Loggable {
void log() const { std::cout << "log: " << static_cast<const T*>(this)->id() << "\n"; }
};
template <typename T>
struct Resettable {
void reset() { static_cast<T*>(this)->set(0); }
};
struct Device : Loggable<Device>, Resettable<Device> {
int v = 5;
int id() const { return v; }
void set(int x) { v = x; }
};
int main() {
Device d;
d.log();
d.reset();
d.log();
return 0;
}混入类也可以添加状态
混入类可以携带自己的数据成员,这些成员会成为组合对象布局的一部分。
#include <iostream>
template <typename T>
struct Tagged {
int tag = 0;
void set_tag(int t) { tag = t; }
int get_tag() const { return tag; }
};
struct Item : Tagged<Item> {
const char* name = "item";
};
int main() {
Item it;
it.set_tag(42);
std::cout << it.name << " " << it.get_tag() << "\n";
return 0;
}混入类与普通继承
CRTP 混入类与普通继承不同,因为基类可以向下调用派生类。普通基类如果没有虚函数,就不能调用仅存在于派生类型中的方法。
基于策略的设计
混入类支持基于策略的设计:通过选择要继承的混入类,在编译时选择行为,这类似于 std::vector 接受分配器策略。
顺序与布局
叠加混入类时,基类的顺序会影响构造顺序和对象布局。请保持混入类彼此独立,使顺序不会影响正确性。
- 按照基类的声明顺序进行构造
- 避免相互依赖的混入类
添加相等性混入类
这里的混入类根据宿主类的相等性合成了 operator!=。
#include <iostream>
template <typename T>
struct EqualityMixin {
friend bool operator!=(const T& a, const T& b) { return !(a == b); }
};
struct P : EqualityMixin<P> {
int x;
P(int v) : x(v) {}
friend bool operator==(const P& a, const P& b) { return a.x == b.x; }
};
int main() {
std::cout << (P(1) != P(2)) << "\n";
return 0;
}条件混入类
您可以将继承与 std::conditional 结合起来,仅在满足条件时包含混入类,在真正的混入类和空混入类之间进行选择。
空基类优化
不包含数据成员的混入类可以受益于空基类优化:编译器不会为它们分配额外字节,因此从大小角度看,叠加许多无状态混入类不会产生开销。
#include <iostream>
template <typename T>
struct A {};
template <typename T>
struct B {};
struct C : A<C>, B<C> {
int x;
};
int main() {
std::cout << (sizeof(C) == sizeof(int)) << "\n";
return 0;
}何时使用混入类
当许多类需要相同的正交行为(日志记录、计数、比较)时,混入类非常适用。它们将每种行为集中在一个地方,并且可以组合而无需运行时开销。
快速检查
请检验您对 CRTP 混入类的掌握程度。
回顾
您学会了使用 CRTP 构建混入类。
- 混入类在编译时组合正交行为
- 它们可以向下调用宿主类,并携带状态
- 无状态混入类得益于空基类优化,不会产生额外开销
- 非常适合日志记录、比较和计数策略
常见问题解答
「使用 CRTP 实现混入」课时是免费的吗?
是的 — 「使用 CRTP 实现混入」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C++ Academy 课程的其余内容,请升级到 CoddyKit PRO。 C++ Academy 课程共包含 4 节课。
「使用 CRTP 实现混入」这节课中我会学到什么?
在编译期组合行为 你通过在浏览器中直接运行的动手代码来练习 C++ Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C++ Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C++ Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「使用 CRTP 实现混入」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C++ Academy 课中编写并运行代码吗?
能。每节 C++ Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。