CRTP#
参考:CRTP
CRTP 英文全称 Curiously Recurring Template Pattern,对应中文翻译为“奇异递归模版模式”。该变成模式在实际代码中应用比较广泛,常用于实现“静态多态”,由于其是靠模版技术在编译期实现的多态,所以有性能高的特点。在很多数学运算的库中有广泛的应用(比如用于线性代数计算的数学库 Eigen)。
示例:
template<typename Z>
class Y {};
class X : public Y<X> {};
类 X
继承自模板类 Y
,并使用模板参数 Z
进行实例化。在这个例子中,Y
被实例化为 Z=X
。
这种模式通常用于解决一些常见的问题,例如实现虚函数、运算符重载等。通过将共同的行为放在基类中,派生类只需要实现特定的功能,而不必重复编写相同的代码。
CRTP 在 C++ 中主要有两种用途:
静态多态(static polymorphism)
添加方法同时精简代码
静态多态#
#include <iostream>
using namespace std;
template <typename Child>
struct Base
{
void interface()
{
static_cast<Child*>(this)->implementation();
}
};
struct Derived : Base<Derived>
{
void implementation()
{
cerr << "Derived implementation\n";
}
};
Derived d;
d.interface();
Derived implementation
这里基类 Base
为模板类,子类 Drived
继承自 Base
同时模板参数为 Drived
,基类中有接口 interface
而子类中则有接口对应实现 implementation
,基类 interface
中将 this
通过 static_cast
转换为模板参数类型,并调用该类型的 implemention
方法。由于 Drived
继承基类时的模板为 Drived
类型所以在 static_cast
时会转换为 Drived
并调用 Drived
的 implemention
方法。(注意这里采用的时 static_cast
而不是 dynamic_cast
,因为只有继承了 Base
的类型才能调用 interface
且这里是向下转型,所以采用 static_cast
是安全的。)
通过 CRTP 可以使得类具有类似于虚函数的效果,同时又没有虚函数调用时的开销(虚函数调用需要通过虚函数指针查找虚函数表进行调用),同时类的对象的体积相比使用虚函数也会减少(不需要存储虚函数指针),但是缺点是无法动态绑定。
template<typename Child>
class Animal
{
public:
void Run()
{
static_cast<Child*>(this)->Run();
}
};
class Dog :public Animal<Dog>
{
public:
void Run()
{
cout << "Dog Run" << endl;
}
};
class Cat :public Animal<Cat>
{
public:
void Run()
{
cout << "Cat Run" << endl;
}
};
template<typename T>
void Action(Animal<T> &animal)
{
animal.Run();
}
Dog dog;
Action(dog);
Cat cat;
Action(cat);
Dog Run
Cat Run
这里 Dog
继承自 Animal
且模板参数为 Dog
,Cat
继承自 Animal
且模板参数为 Cat
,Animal
,Dog
,Cat
中都声明了 Run
,而 Animal
中的 Run
是通过类型转换后调用模板类型的 Run
方法实现的。在 Action
模板函数中接收 Animal
类型的引用(或指针)并在其中调用了 animal
对象的 Run
方法,由于这里传入的是不同的子类对象,因此 Action
中的 animal
也会有不同的行为。
添加方法,减少冗余#
假设现在需要实现一个数学运算库,以支持 Vector2
,Vector3
,Vector4
… 等类型,如果我们将每个类分别声明并实现如下:
//Vec3
struct Vector3
{
float x;
float y;
float z;
Vector3() = default;
Vector3(float _x, float _y, float _z);
inline Vector3& operator+=(const Vector3& rhs);
inline Vector3& operator-=(const Vector3& rhs);
//....
};
inline Vector3 operator+(const Vector3& lhs, const Vector3& rhs);
inline Vector3 operator-(const Vector3& lhs, const Vector3& rhs);
//....
//Vec2
struct Vector2
{
float x;
float y;
Vector2() = default;
Vector2(float _x, float _y);
inline Vector2& operator+=(const Vector2& rhs);
inline Vector2& operator-=(const Vector2& rhs);
//....
};
inline Vector2 operator+(const Vector2& lhs, const Vector2& rhs);
inline Vector2 operator-(const Vector2& lhs, const Vector2& rhs);
//....
我们会发现需要为每个类型都实现 +=
、-=
、++
、--
、+
、-
等运算符重载,而且每个类型的一些运算符,行为都很类似,而且可以使用其他的运算符进行实现,比如 +=
、-=
、++
、--
都可以采用 +
、-
运算符进行实现。这时我们就可以采用 CRTP 抽离出这些共同的类似方法,减少代码的冗余:
template<typename T>
struct VectorBase
{
T& underlying() { return static_cast<T&>(*this); }
T const& underlying() const { return static_cast<T const&>(*this); }
inline T& operator+=(const T& rhs)
{
this->underlying() = this->underlying() + rhs;
return this->underlying();
}
inline T& operator-=(const T& rhs)
{
this->underlying() = this->underlying() - rhs;
return this->underlying();
}
//.....
};
struct Vector3 : public VectorBase<Vector3>
{
float x;
float y;
float z;
Vector3() = default;
Vector3(float _x, float _y, float _z)
{
x = _x;
y = _y;
z = _z;
}
};
inline Vector3 operator+(const Vector3& lhs, const Vector3& rhs)
{
Vector3 result;
result.x = lhs.x + rhs.x;
result.y = lhs.y + rhs.y;
result.z = lhs.z + rhs.z;
return result;
}
inline Vector3 operator-(const Vector3& lhs, const Vector3& rhs)
{
Vector3 result;
result.x = lhs.x - rhs.x;
result.y = lhs.y - rhs.y;
result.z = lhs.z - rhs.z;
return result;
}
//......
int main()
{
Vector3 v0(6.0f, 5.0f, 4.0f);
Vector3 v2(4.0f, 5.0f, 6.0f);
v0 += v2;
v0 -= v2;
return 0;
}
通过把 +=
, -=
等操作放到基类中并采用 +
、-
运算符实现,这样一来所有继承自 VectorBase
的类,只要其定义了 +
、-
运算符就可以自动获得 +=
、-=
等运算符,这样大大的减少了代码中的冗余。
在有多个类型存在相同方法,且这些方法可以借助于类的其他方法进行实现时,均可以采用 CRTP 进行精简代码。