Something About Curiously Recurring Template Pattern(CRTP)
Contents
Intro
奇异递归模板模式(curiously recurring template pattern,CRTP),是 C++ 模板编程时的一种惯用法,即将派生类作为基类的模板参数。
本文所涉及的代码示例地址:Dup4/learning-cpp
General Form
// The Curiously Recurring Template Pattern (CRTP)
template <class T>
class Base {
    // methods within Base can use template to access members of Derived
};
class Derived : public Base<Derived> {
    // ...
};
CRTP 的起手式一般如上所示。
下面会介绍一些 CRTP 的用例,比如静态多态。
Dynamic polymorphism
为了更好的介绍 CRTP,我们先回顾一下使用虚函数实现动态多态的基本写法。
#include <iostream>
using namespace std;
class Base {
public:
    virtual void Show() const {
        cout << "Base" << endl;
    }
};
class Derived : public Base {
public:
    void Show() const override {
        Base::Show();
        cout << "Derived" << endl;
    }
};
void f(const Base& base) {
    base.Show();
}
int main() {
    auto d = Derived();
    d.Show();
    f(d);
    return 0;
}
输出结果:
一般使用 virtual 关键字来在基类声明一个虚函数,在子类中去 override 它,在运行时,通过虚表找到指定的函数去执行,这样会存在运行时的开销。
而且派生类的 override 是覆写,如果想调用基类原本的 Show 函数,还得在派生类中调用一下,这时可以认为 主动权 掌握在派生类手上。
Static polymorphism
我们可以利用 CRTP 来实现静态多态。
#include <iostream>
using namespace std;
template <typename T>
class Base {
public:
    void Show() const {
        cout << "Base" << endl;
        static_cast<const T&>(*this).Show();
    }
};
class Derived : public Base<Derived> {
public:
    void Show() const {
        cout << "Derived" << endl;
    }
};
template <typename T>
void f(const Base<T>& base) {
    base.Show();
}
int main() {
    auto d = Derived();
    d.Show();
    f(d);
    return 0;
}
输出结果:
由于基类能够在编译期知道派生类的类型,那么通过 static_cast 就可以将自己转换成派生类的类型,并且访问派生类的函数。
这时可以发现,调用 Show 函数的入口,其实是基类中的 Show,多态的行为是基类中 Show 函数进行控制的。
可以认为 主动权 掌握在基类手上。
想要实现多态也能简单,只需要在函数中声明基类类型时使用模板即可,那么就可以传入不同的派生类类型,来达到多态的目的。
那如果我们想用一个 vector 来存不同的派生类呢?
Static polymorphism combined with dynamic polymorphism
由于 vector 只能存储同一类型,对于动态多态,我们只需要存储基类的指针,即可在 vector 中存储不同派生类转化成的基类指针。
但是在 CRTP 中实现的静态多态中,基类是个模板类,好像就不太可行了。
#include <iostream>
#include <vector>
using namespace std;
class Base {
public:
    virtual ~Base() {}
    virtual void Show() const = 0;
};
template <typename T>
class BaseCRTP : public Base {
public:
    void Show() const override final {
        showImpl();
        static_cast<const T&>(*this).showImpl();
    }
private:
    void showImpl() const {
        cout << "Base" << endl;
    }
};
class Derived : public BaseCRTP<Derived> {
    friend class BaseCRTP<Derived>;
private:
    void showImpl() const {
        cout << "Derived" << endl;
    }
};
void f(const Base& b) {
    b.Show();
}
int main() {
    Derived d;
    d.Show();
    f(d);
    vector<Base*> v;
    v.push_back(&d);
    v[0]->Show();
    return 0;
}
输出结果:
一个可行的方法是,动静结合。
即在 CRTP 基类之上,再声明一个基类,这两个类型之间用动态多态。
虽然仍然存在运行时的开销,但是相比于全链路动态多态,这种 动静结合 的方式,开销还是要小一些。
Object counter
使用 CRTP,我们能够轻易的实现元素构造的计数。
#include <iostream>
using namespace std;
template <typename T>
class Counter {
public:
    static inline int objects_created = 0;
    static inline int objects_alive = 0;
    Counter() {
        ++objects_created;
        ++objects_alive;
    }
    Counter(const Counter&) {
        ++objects_created;
        ++objects_alive;
    }
protected:
    ~Counter() {
        --objects_alive;
    }
};
class X : Counter<X> {
public:
};
class Y : Counter<Y> {
public:
};
int main() {
    X x;
    Y y;
    cout << Counter<X>::objects_created << endl;
    cout << Counter<Y>::objects_created << endl;
}
输出结果:
Polymorphic chaining
这个要解决的问题是说,对 Class 中的函数进行链式调用时,如果调用链中既有基类函数(未被派生类覆写的函数),也有派生类函数,那么这两类函数返回的 this 是不一样的,一旦某个节点返回的 this 是基类类型,那么在这个链式调用的后续,就无法再调用派生类的独有方法。
但是 CRTP 能解决这个问题,因为基类能够拿到派生类的类型,可以将 this 转换成派生类类型。
#include <iostream>
using namespace std;
enum class Color {
    RED = 0,
    GREEN = 1,
    BLUE = 2,
};
template <typename ConcretePrinter>
class Printer {
public:
    Printer(ostream& pstream) : m_stream(pstream) {}
    template <typename T>
    ConcretePrinter& Print(T&& t) {
        m_stream << t;
        return derived();
    }
    template <typename T>
    ConcretePrinter& Println(T&& t) {
        m_stream << t << endl;
        return derived();
    }
private:
    ConcretePrinter& derived() {
        return static_cast<ConcretePrinter&>(*this);
    }
    ostream& m_stream;
};
class CoutPrinter : public Printer<CoutPrinter> {
public:
    CoutPrinter() : Printer(cout) {}
    CoutPrinter& SetConsoleColor(Color c) {
        color_ = c;
        return *this;
    }
private:
    Color color_;
};
int main() {
    CoutPrinter().Print("a").SetConsoleColor(Color::BLUE).Println("b");
    return 0;
}
输出结果:
Polymorphic copy construction
使用 CRTP 也能轻易实现派生类的深拷贝,而不用为每个派生类都实现一遍。
其实参考这个 case,可以发现,CRTP 基于模板,有效的使用它,可以比动态多态减少更多的代码重复。
#include <iostream>
#include <memory>
using namespace std;
template <typename T>
class Base {
public:
    void Show() const {
        cout << "Base" << endl;
        derived().Show();
    }
    std::unique_ptr<Base> Clone() const {
        return std::make_unique<T>(static_cast<T const&>(*this));
    }
protected:
    const T& derived() const {
        return static_cast<const T&>(*this);
    }
    Base() = default;
    Base(const Base&) = default;
    Base(Base&&) = default;
};
class Derived : public Base<Derived> {
public:
    void Show() const {
        cout << "Derived" << endl;
    }
};
int main() {
    auto s = Derived();
    s.Show();
    auto ss = s.Clone();
    ss->Show();
    return 0;
}
输出结果:
Reference
- https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
 - http://www.vishalchovatiya.com/crtp-c-examples/
 
Created: April 27, 2022