Curiously recurring template pattern

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的私有构造函数.故而编译错误。

Published:
2015-09-14 01:00
Category:
Tag:
CPP15