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