You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++23中何时优先选用CRTP而非显式对象参数?

问题简述

C++23提供了一种编写混入类(mixin class)的新方式以替代CRTP,是否存在仍需优先选用CRTP的场景?

两种方案概述

CRTP是C++中一种强大的惯用法,利用模板在编译期自动生成特定类型的行为,常被用于创建混入类。

C++23引入了显式对象参数(explicit object parameters),允许在非静态成员函数的参数列表中显式引用类名,使得模板非静态成员函数可访问派生类,无需将混入类特化于派生类。

以下示例展示新旧两种方案:

// 经典CRTP(旧方案)
template <typename T>
class MyMixin {
    // ...
    void foo() {
        // 使用T的相关操作...
    }
};

// 必须将MyMixin特化为MyType
class MyType : public MyMixin<MyType> {
    // ...
};



// 显式对象参数(C++23新方案)
class MyMixin {
    // ...
    template <typename T>
    void foo(this T* self) {
        // 使用T的相关操作...
    }
};

// 更简洁的混入类语法
class MyType : public MyMixin {
    // ...
};

显然新方案让派生类的语法更简洁,比如可以避免如下复杂写法:

template<typename x, typename y, typename z, typename w>
class MyWackyContainer : public MyMixin<MyWackyContainer<x,y,z,w>>

因为新方案无需为MyMixin提供模板参数。

完整问题

根据相关论文摘要:

虽然[添加显式对象参数]已经消除了很多场景下对CRTP的需求,但仍然存在必须使用该模式才能实现的情况。

目前能想到的CRTP与新方案的唯一功能差异是,CRTP可在声明非函数成员和静态成员函数时引用类型。但这种特性是否存在实际有用的场景?

(次要问题:我的理解是否正确?有没有遗漏的点?)


优先选用CRTP的场景

你的理解基本准确,但还有不少实际场景需要优先选择CRTP:

1. 定义依赖派生类类型的非函数成员

CRTP可以在混入类中直接声明依赖派生类类型的成员变量、类型别名等,这是显式对象参数方案做不到的。比如:

template <typename Derived>
class CRTPMixin {
public:
    // 基于派生类的类型别名
    using ValueType = typename Derived::Value;
    // 存储派生类对象的容器
    std::vector<ValueType> cache;

    void update_cache() {
        auto self = static_cast<Derived*>(this);
        cache.push_back(self->get_value());
    }
};

class MyType : public CRTPMixin<MyType> {
public:
    using Value = int;
    int get_value() { return 42; }
};

这里ValueType和cache成员直接依赖派生类的类型,显式对象参数方案无法在类级别定义这类成员,只能在函数内部临时处理,无法复用这些类型或成员。

2. 静态成员函数与静态成员变量

CRTP的混入类可以定义依赖派生类类型的静态成员,而显式对象参数仅适用于非静态成员函数。比如实现一个类型计数器:

template <typename Derived>
class TypeCounter {
public:
    static inline size_t instance_count = 0;

    TypeCounter() { ++instance_count; }
    ~TypeCounter() { --instance_count; }

    // 静态方法返回当前类型的实例数
    static size_t get_count() { return instance_count; }
};

class MyTypeA : public TypeCounter<MyTypeA> {};
class MyTypeB : public TypeCounter<MyTypeB> {};

每个派生类会拥有独立的instance_count静态变量,这是显式对象参数方案无法实现的——因为显式对象参数的函数是模板非静态成员,无法绑定到每个派生类的静态上下文。

3. 编译期接口约束与概念检查

CRTP可以在混入类中直接对派生类施加编译期约束,比如用static_assert检查派生类是否拥有特定成员:

template <typename Derived>
class CRTPMixin {
public:
    CRTPMixin() {
        static_assert(std::is_member_function_pointer_v<decltype(&Derived::required_method)>,
                      "Derived class must implement required_method");
    }
};

class ValidType : public CRTPMixin<ValidType> {
public:
    void required_method() {}
};

// 编译错误:Derived class must implement required_method
class InvalidType : public CRTPMixin<InvalidType> {};

虽然显式对象参数的函数内部也可以做类似检查,但CRTP的约束是在混入类的构造阶段就生效,能更早发现问题,且可以在类级别统一约束,而不是每个函数都重复检查。

4. 实现静态多态的完整抽象

CRTP常用于实现静态多态,此时混入类可以作为基类提供统一的接口,同时让派生类实现具体逻辑。显式对象参数方案虽然能让函数访问派生类,但无法在类级别定义统一的接口契约,也无法实现类似基类指针/引用的静态多态调度(当然CRTP本身也是编译期调度,但可以通过模板统一处理)。

关于你的理解补充

你提到的“CRTP可在声明非函数成员和静态成员函数时引用类型”是核心差异之一,但除此之外,CRTP还能提供编译期约束、静态多态的完整抽象等能力,这些都是显式对象参数方案无法替代的。

内容的提问来源于stack exchange,提问作者user1447460

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:43:15