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


前言

CRTP是C++模板编程时的一种惯用法(idiom):把派生类作为基类的模板参数。更一般地被称作F-bound polymorphism。

CRTP在C++中主要有两种用途:

  1. 静态多态(static polymorphism)
  2. 添加方法同时精简代码

一、静态多态

先看一个简单的例子:

#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";
	}
};

int main()
{
	Derived d;
	d.interface();  // Prints "Derived implementation"

	return 0;
}

这里基类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();
}

int main()
{
	Dog dog;
	Action(dog);

	Cat cat;
	Action(cat);
	return 0;
}

这里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进行精简代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值