奇异递归模板模式( Curiously Recurring Template Pattern,CRTP)1

1.CRTP介绍

奇异递归模板模式(curiously recurring template pattern,CRTP)是C++模板编程时的一种惯用法(idiom):把派生类作为基类的模板参数。更一般地被称作F-bound polymorphism,是一类F 界量化,相关介绍可以参考 wiki 奇异递归模板模式。curiously recurring template pattern,CRTP的来源,请参考这篇C++ Report James Coplien in 1995

1.1 CRTP的特点

  1. 继承自模板类;
  2. 使用派生类作为模板参数特化基类;

1.2 CRTP基本范式

CRTP如下的代码样式:

template <typename T>
class Base
{
    ...
};
 // use the derived class itself as a template parameter of the base class
class Derived : public Base<Derived>
{
    ...
};

这样做的目的是在基类中使用派生类,从基类的角度来看,派生类其实也是基类,通过向下转换[downcast],因此,基类可以通过static_cast把其转换到派生类,从而使用派生类的成员,形式如下:

template <typename T>
class Base
{
public:
    void doWhat()
    {
        T& derived = static_cast<T&>(*this);
        // use derived...
    }
};

注意 这里不使用dynamic_cast,因为dynamic_cast一般是为了确保在运行期(run-time)向上向下转换的正确性。CRTP的设计是:派生类就是基类的模板参数,因此static_cast足矣。另,关于cpp几种转换的讨论参见这个When should static_cast, dynamic_cast, const_cast and reinterpret_cast be used?

1.3 易错点

  1. 当两个类继承自同一个CRTP base类时,如下代码所示,会出现错误(Derived2派生的基类模板参数不是Derived2)。
class Derived1 : public Base<Derived1>
{
    ...
};
class Derived2 : public Base<Derived1> // bug in this line of code
{
    ...
};

为了防止种错误的出现,可以写成如下的代码形式:

template <typename T>
class Base
{
public:
    // ...
private:// import 
    Base(){};
    friend T;
};

通过代码可以看出来,基类中添加一个私有构造函数,并且模板参数T是Base的友元。这样做可行是因为,派生类需要调用基类的构造函数(编译器会默认调用的..),由于Base的构造函数是私有的(private),除了友元没有其他类可以访问的,而且基类唯一的友元是其实例化模板参数。

  1. 派生类会隐藏和基类同名的方法,如下代码所示:
template <typename T>
class Base
{
public:
    void do();
};
class Derived : public Base<Derived>
{
public:
    void do(); // oops this hides the doSomething methods from the base class !
}

出现这个情况的缘由是,以作用域为基础的“名称遮掩规则”,从名称查找的观点来看,如果实现了Derived::do(), 则Base::do不再被Derived继承(哎呀,因为被继承类的实现方法遮掩了,即基类的 do方法被隐藏了),详情请见 Effective C++ Item 33。

2. CRTP使用demo

CRTP有几种使用方法,下面介绍CRTP两种常用方法:1. Adding functionality(添加功能,通过继承基类(CRTP)来扩展派生类的接口); 2. Static Interfaces(利用静态多态,编译期多态),见相关问题讨论 CRTP to avoid dynamic polymorphism,来段实例代码先:

//  The Curiously Recurring Template Pattern (CRTP)

#include <iostream>
#include <cmath>
#include <cassert>
#include <string>
using namespace std;

template <class T>
struct Expression
{
    const T& cast() const {
        return static_cast<const T&>(*this);
    }

    T& cast() {
        return static_cast<T&>(*this);
    }
    // expands derived class interface by inheriting from the base class
    void set_absolute_value() {
        cast().set_value(abs(cast().get_value()));
    }

    double value() const {
        return cast().get_value();
    }

    double result() const {
        return cast().get_result();
    }

    void calc() {
        cast().sub_calc();
    }

    Expression& operator=(const Expression&) = delete;

private:
    Expression(){}
    friend T;
};

struct Square : public Expression<Square>
{
    Square(double val = 0, double res = 0):val_(val),result_(res){}
    void set_value(const double& val) {
        val_ = val;
    }
    double get_value() const {
        return val_;
    }

    double get_result() const {
        return result_;
    }

    void sub_calc() {
        result_ = val_*val_;
    }
private:
    double val_;
    double result_;
};

struct Sqrt : public Expression<Sqrt>
{
    Sqrt(double val = 0, double res = 0) :val_(val), result_(res) {}
    void set_value(const double& val) {
        val_ = val;
    }
    double get_value() const {
        return val_;
    }

    double get_result() const {
        return result_;
    }

    void sub_calc() {
        assert(val_>=0);
        result_ = sqrt(val_);
    }
private:
    double val_;
    double result_;
};

// Static interfaces
template<class T> // T is deduced at compile-time
void OP(Expression<T> &sq, const string& op) {
    // will do static dispatch
    cout << "Berfore OP this expression's value is:" << sq.value() << "\n";
    sq.calc();
    cout << "After OP "<< op <<\
        ",  this expression's result is:" << sq.result() << "\n";
}

int main() {

Sqrt sqr(-2);
cout << "this expression's value is:" << sqr.value() << "\n";
// Adding functionality
sqr.set_absolute_value();
cout << "After set absolute,  this expression's value is:" << sqr.value() << "\n";
OP(sqr, "Sqrt");
cout << "---------------------------\n";
Square sq(-2);
OP(sq, "Square");

return 0;
}

执行结果:
CRTP test

分析:通过上面的程序我们可以看到,继承类 Square,Sqrt 的成员通过继承基类Expression来扩展自己的接口,如基类的set_absolute_value函数,因此,这就是所谓的Adding functionality,从这个角度来看,基类不是接口,继承类不是实现,然而这里恰恰相反,基类使用继承类的方法(get_value and set_value),就这一点而言,继承类给基类提供接口,这和传统的继承有点不同。关于Static interfaces,其实就是一种静态多态(static polymorphism),我们利用相同的接口: void OP(Expression<T> &sq, const string& op)分别调用不同的派生类Square,Sqrt,表现出不同的行为,这些多态调用在编译期就确定了,这样可以避免动态多态虚函数调用的效率消耗。

3. CRTP特点总结

CRTP是一种静态多态(static polymorphism/Static binding/Compile-Time binding)与其对应的是动态多态(dynamic polymorphism/Dynamic binding/Run-Time binding)。
静态多态与和动态的区别是:多态是动态绑定(运行时绑定 run-time binding),CRTP是静态绑定(编译时绑定 compile-time binding)。其中,动态多态在实现多态时,需要重写虚函数,这种运行时绑定的操作往往需要查找虚表等,效率低。另,template的核心技术在于编译期多态机制,与运行期多态(runtime polymorphism)相比,这种动态机制提供想编译期多态性,给了程序运行期无可比拟的效率优势,关于编译期编译期多态和运行期多态效率对比一篇不错的文章,参见The cost of dynamic (virtual calls) vs. static (CRTP) dispatch in C++(这个链接有可能被墙,请翻墙)。因此,如果想在编译期确定通过基类来得到派生类的行为,CRTP便是一种绝佳的选择。

最近在学习AD(automatic differentiation,自动微分),相关autodiff工具点这个链接。发现还是有相当一部分这种库在使用CRTP技术,这是由于在数值计算中,对于不同的模型会使用不同的方法,一般使用继承提供统一接口,但又希望不损失效率,因此,此时便可利用CRTP了,子类的operator(expression..)实现将覆盖基类的operator实现,并可以编译期静态绑定至子类的方法,AD自动求导效率堪比手动写出相关程序(所谓的 Hand coded),有机会的话会写一篇AD方便的文章。其实很多C++工程都用到CRTP技术了,比如std::enable_shared_from_this 大法

参考:

  1. wiki 奇异递归模板模式
  2. Modern C++ design
  3. The Curiously Recurring Template Pattern
<think>好的,我现在需要帮助用户理解奇异递归模板模式CRTP)的定义、实现和示例。用户已经提供了三个引用资料,我需要结合这些内容来组织回答。 首先,CRTP的定义。根据引用[1]和[2],CRTP是一种C++设计模式,基类模板将派生类作为模板参数。基类通过模板参数访问派生类的成员,实现静态多态。需要明确说明基类和派生类的关系,特别是自引用的特点。 接下来是实现部分。引用[3]中的代码示例展示了基类模板如何接受派生类作为参数,并在基类中使用static_cast将this转换为派生类指针。这样基类可以调用派生类的方法,或者派生类重写基类的函数。需要分步骤说明基类模板的定义、派生类的继承方式,以及如何通过static_cast实现类型转换。同时,引用[1]提到的链式调用可能也是实现的一个方面,可以结合说明。 然后是示例部分。需要提供一个简单的代码示例,展示CRTP的具体应用。比如基类有某个方法,派生类实现后,基类通过模板参数调用派生类的方法。比如计数器模式,基类记录对象数量,派生类继承并自动更新计数。或者像引用[3]中的print函数,派生类重写基类的print方法,基类调用时显示不同信息。 另外,需要注意用户提到的行内公式和独立公式的格式要求。不过目前看来,CRTP的内容可能不涉及数学公式,但如果有涉及的地方需要正确使用LaTeX格式。 用户还要求生成相关问题,我需要考虑CRTP的常见应用场景、优缺点、与其他模式的区别,以及实际项目中的使用案例等。 现在检查引用内容是否准确。引用[1]提到基类返回派生类引用,引用[2]说明基类模板接受派生类作为参数,引用[3]给出了具体的代码结构。需要确保这些内容在回答中被正确引用,并在适当的位置添加引用标识。 可能需要注意术语的一致性,比如CRTP的不同称呼,确保使用用户提供的资料中的术语。同时,解释CRTP如何实现静态多态,与动态多态的区别,以及其优势(如性能提升,编译时多态)。 最后,结构要清晰,分点回答定义、实现、示例,每个部分简明扼要,代码示例正确,并符合用户的格式要求。</think>### 奇异递归模板模式CRTP)解析 #### 1. **定义** 奇异递归模板模式Curiously Recurring Template Pattern, CRTP)是C++中一种**静态多态**设计模式,其核心思想是**基类模板以派生类作为模板参数**,从而实现基类对派生类成员的直接访问[^2]。 这种模式通过模板的自引用特性(基类依赖派生类的类型)实现编译时的多态行为,避免运行时虚函数开销[^3]。 --- #### 2. **实现** CRTP的实现分为以下关键步骤: 1. **基类模板定义**:基类模板接受一个类型参数`Derived`(实际为派生类)。 2. **派生类继承**:派生类通过`public Base<Derived>`递归继承基类模板。 3. **类型转换**:基类通过`static_cast<Derived*>(this)`将自身指针转换为派生类指针,调用派生类方法。 **代码示例(基础结构)**: ```cpp template <typename Derived> class Base { public: void interface() { static_cast<Derived*>(this)->implementation(); // 调用派生类方法 } }; class Derived : public Base<Derived> { public: void implementation() { /* 具体实现 */ } }; ``` --- #### 3. **示例:对象计数器** 以下示例通过CRTP统计某个类的实例数量: ```cpp template <typename T> class Counter { public: static int count; Counter() { ++count; } ~Counter() { --count; } static int getCount() { return count; } }; template <typename T> int Counter<T>::count = 0; // 派生类继承基类模板并将自身作为参数传递 class MyClass : public Counter<MyClass> { /* 类定义 */ }; // 使用 MyClass obj1, obj2; std::cout << MyClass::getCount(); // 输出2 ``` 此例中,`Counter`基类通过模板参数`T`区分不同派生类的计数器,实现编译时类型隔离。 --- #### 4. **核心优势** - **零运行时开销**:静态多态无需虚函数表(vtable)。 - **编译时多态**:类型检查和优化在编译阶段完成。 - **链式调用支持**:基类方法返回派生类引用,支持链式语法(如`obj.func1().func2()`)[^1]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值