CRTP介绍
CRTP的全称为Curiously recurring template pattern,描述的是C++中的一种模板应用模式。其示例代码可以抽象为这样的:
// 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> { // ... };
这种继承模式看起来非常绕:父类居然需要知道子类的类型。但是由于C++中的模板对于类型来说并没有强加什么限制,只要知道名字就可以。而编译器parse到这个继承文法的时候,子类的类型已经注册,因此这样的继承关系是可行的。
这样的代码看上去蛋疼无比,但是这样的代码演变为一种模式,一定有其特别的地方。所以下文我们就来探究一下其特别之处。
CRTP模拟虚函数
在C++中,虚函数是利用虚函数表来实现的。当调用一个虚函数时,首先获得虚表指针,然后在虚函数表中查找对应的函数指针,最后根据得到的函数指针来进行调用。完整的执行流程中,涉及到多次指针跳转,所以虚函数的开销还是比较大的。而利用CRTP这种模板继承来实现虚函数则可以避免这种查表的负担,其事例代码如下:
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(); };
这里使用的主要机制就是强制类型转换,如果不涉及到虚继承,强制类型转换基本可以在编译期完成。
CRTP实现对象类型计数
有时情况下,我们需要知道特定类型的活跃对象数目。类似于引用计数,只不过是针对类型而言,而不是针对特定对象。通过继承一个counter
类,来存储其活跃对象数和总创建对象数。
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> { // ... };
通过这两个静态计数器,我们就可以得到类型X的创建销毁的统计信息。
通过CRTP实现对象引用计数
上面的那个例子是针对特定类型的对象创建和销毁计数,事实上我们还可以将单一对象的引用计数也实现。其实也不是完整实现,而只是通过shared_ptr
来实现自动生命周期管理,计数方面还是由shared_ptr
内部维持。
事实上,如果我们只使用shared_ptr
也能达到自动生命周期管理的效果。但是有这样的一种情况,shared_ptr
是无法满足的,即内部函数需要异步执行的时候。此时该内部异步函数需要获得当前对象的shared_ptr
拷贝,但是类内部对象是无法得到这个拷贝的,只能得到this
指针。而从this
指针我们无法推演出对应的shared_ptr
的地址。所以,为了实现这个功能,我们需要内部嵌入一个函数来获得该智能指针备份。这就是enable_shared_from_this
的典型应用场景,目前见过的都是在网络连接管理的时候使用,例如Boost.Asio
。
template<class _Ty> class enable_shared_from_this { // provide member functions that create shared_ptr to this public: typedef _Ty _EStype; shared_ptr<_Ty> shared_from_this() { // return shared_ptr return (shared_ptr<_Ty>(_Wptr)); } };
这样,我们就可以从this->shared_from_this
中获得原始的shared_ptr
的指针,当然前提是this
是通过shared_ptr
来管理的。
其主要原理是enable_shared_from_this<T>
中存储了一个weak_ptr<T>
。而shared_ptr
的构造函数中有一个是从weak_ptr<T>
来转换的,所以我们就可以通过shared_from_this
来从这个保存的weak_ptr<T>
来获得shared_ptr<T>
。之所以不保存shared_ptr
,是为了防止自引用。自引用的情况下,对象无法析构,从而导致资源泄漏。
使用CRTP实现不可继承类
在C++
中,实现不可继承类主要通过的是将构造函数私有化,同时定义一个静态函数来调用该构造函数。
class Taijian{ public: static Taijian* publicFunc() { Taijian* a=new Taijian; return a; } private: Taijian() { cout<<"taijian created"<<endl; } ~Taijian() { cout<<"taijian deleted"<<endl; } };
但是这种实现由一个问题,就是如果子类也定义一个静态函数来调用父类的静态函数的话,就可以绕过该限制。如果子类大小与父类大小等同的话,就不会出现任何问题。
class NoTaijian:public Taijian { public: static NoTaijian* publicFunc() { return reinterpret_cast<NoTaijian*>(Taijian::publicFunc()); } private: NoTaijian()=delete; ~NoTaijian()=delete; };
所以,之前的解决方案并不是完美的。下面我们通过CRTP
和友元类来实现完美的final
方案。
template< typename TDerive, typename TProvider > class CFobidDeriveProviderBase { friend TDerive; friend TProvider; private: CFobidDeriveProviderBase(){} ~CFobidDeriveProviderBase(){} }; /* * 提供禁止派生的功能,需要此功能的类可以从CFobidDeriveProvider派生,并将类名作为模板参数传递 */ template< typename TDerive > class CFobidDeriveProvider : virtual public CFobidDeriveProviderBase< TDerive, CFobidDeriveProvider<TDerive>> { public: CFobidDeriveProvider(){} ~CFobidDeriveProvider(){} }; /* * 测试类,该类不可被继承 */ class CNoDerive : public CFobidDeriveProvider< CNoDerive > { public: CNoDerive(){} ~CNoDerive(){} void Alert() { AtlMessageBox( NULL, _T("Alert") ); } };
如果有类CSomeDerive
继承 CNoDerive
,则无法构造。CSomeDerive
的构造函数调用过程如下:由于CFobidDeriveProvider
是从CFobidDeriveProviderBase
虚拟派生,在虚继承出现的继承层次中,总是在构造非虚基类之前构造虚基类,因而会跳过CNoDerive和CFobidDeriveProvider
的构造函数而直接调用CFobidDeriveProviderBase
的构造函数,但CSomeDerive
不是CFobidDeriveProviderBase
的友元,因此也无法调用
CFobidDeriveProviderBase
的私有构造函数.故而编译错误。