0Pricing
C++ Academy · 课时

使用 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. CRTP 惯用法
  2. 静态多态
  3. 使用 CRTP 实现混入
  4. 何时使用 CRTP
← 返回 C++ Academy