CRTP 奇异递归模板模式

2023年2月12日 655点热度 0人点赞 0条评论

奇异递归模板模式(curiously recurring template pattern,CRTP)是C++模板编程时的一种常见的做法,即把派生类作为基类的模板参数。

一般的形式为

template <class T> 
struct Base
{
    void interface()
    {
        // ...
        static_cast<T*>(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        T::static_sub_func();
        // ...
    }
};

struct Derived : Base<Derived>
{
    void implementation();
    static void static_sub_func();
};

其主要表现是派生类将自身作为模板参数传给模板类

这样做的目的是实现静态的多态,例如

template <class T> 
struct Base
{
    void interface()
    {
        // ...
        static_cast<T*>(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        T::static_sub_func();
        // ...
    }
};

struct Derived : Base<Derived>
{
    void implementation();
    static void static_sub_func();
};

这里利用了基类模板的成员函数只有被调用的时候才会被编译器实例化的原理,提前声明了interface,当interface被调用的时候,派生类的implementation一定已经声明过了。因为此时的基类就是派生类本身,因为基类的模板参数是派生类本身,只需使用static_cast就可以实现静态的多态调用了,也正是因为没有虚表等概念,所以其优点是省去了动态多态中虚函数的查询、调用等开销的代价,不过也因此CRTP并没有在运行时的动态绑定(毕竟叫静态多态)。

例1:对象计数

统计一个类的实例对象创建与析构的数据

template <typename T>
struct counter
{
    static int objects_created;
    static int objects_alive;

    counter()
    {
        ++objects_created;
        ++objects_alive;
    }
    
    counter(const counter&)
    {
        ++objects_created;
        ++objects_alive;
    }
protected:
    ~counter() // objects should never be removed through pointers of this type
    {
        --objects_alive;
    }
};
template <typename T> int counter<T>::objects_created( 0 );
template <typename T> int counter<T>::objects_alive( 0 );

class X : counter<X>
{
    // ...
};

class Y : counter<Y>
{
    // ...
};

这里利用模板的特性实现了不同实例的单独计数,每当X、Y实例化时会调用各自的计数器,X调用counter<X>,Y调用counter<Y>

例2:多态复制构造

当使用多态时,常需要基于基类指针创建对象的一份拷贝。常见办法是增加clone虚函数在每一个派生类中。使用CRTP,可以避免在派生类中增加这样的虚函数。

以前的做法可能是

class Shape
{
public:
	virtual ~Shape() {}
	virtual Shape* clone() const = 0;
};

class Square : public Shape
{
public:
	Shape* clone() const
	{
		return new Square(*this);
	}
};

class Circle : public Shape
{
public:
	Shape* clone() const
	{
		return new Circle(*this);
	}
};

现在则可以

class Shape {
public:
	virtual ~Shape() {}
	virtual Shape* clone() const = 0;
};

template <typename Derived>
class Shape_CRTP : public Shape {
public:
	virtual Shape* clone() const {
		return new Derived(static_cast<Derived const&>(*this));
	}
};

class Square : public Shape_CRTP<Square>
{

};

class Circle : public Shape_CRTP<Circle>
{

};
例3:不可派生的类

一个类如果不希望被继承,类似于Java中的具有finally性质的类,这在C++中可以用虚继承来实现:

template<typename T> class MakeFinally{
   private:
       MakeFinally(){}//只有MakeFinally的友类才可以构造MakeFinally
       ~MakeFinally(){}
   friend T;
};

class MyClass:public virtual  MakeFinally<MyClass>{};//MyClass是不可派生类

//由于虚继承,所以D要直接负责构造MakeFinally类,从而导致编译报错,所以D作为派生类是不合法的。
class D: public MyClass{};
//另外,如果D类没有实例化对象,即没有被使用,实际上D类是被编译器忽略掉而不报错

int main()
{
MyClass var1;
// D var2;  //这一行编译将导致错误,因为D类的默认构造函数不合法
}

个人感觉CRTP的派生类本来就不太适合继续继承

例4:单例模式
template<typename T>
class Singleton
{
public:
	T* GetInstancePtr()
	{
		if (m_ptr == nullptr)
		{
			m_ptr = new T;
            // do something to initialize
		}
		return m_ptr;
	}

	void Delete()
	{
		if (m_ptr != nullptr)
		{
			delete m_ptr;
			m_ptr = nullptr;
		}
	}

	static T* m_ptr = nullptr;

private:
	Singleton();
	Singleton(const Singleton&);
};

一个很方便的用来实现单例模式的模板


参考

 

icebmji

这个人很懒,什么都没留下