《奇異遞迴模板模式(Curiously Recurring Template Pattern)》
weak_ptr 是關鍵:
吉良吉影 最近很忙,暫停更新...
本篇短文將簡短的介紹奇異遞迴模板模式(Curiously Recurring Template Pattern,CRTP),CRTP是C++模板程式設計時的一種慣用法(idiom):把派生類作為基類的模板引數。更一般地被稱作F-bound polymorphism。
1980年代作為F-bound polymorphism被提出。Jim Coplien於1995年稱之為CRTP。
CRTP在C++中主要有兩種用途:
- 靜態多型(static polymorphism)
- 新增方法同時精簡程式碼
1.靜態多型
先看一個簡單的例子:
#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也會有不同的行為。
2.新增方法,減少冗餘
假設現在我們需要實現一個數學運算庫,我們需要支援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.