Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Curiously recurring template pattern - spirits away #11882

Open
guevara opened this issue Nov 13, 2024 · 0 comments
Open

Curiously recurring template pattern - spirits away #11882

guevara opened this issue Nov 13, 2024 · 0 comments

Comments

@guevara
Copy link
Owner

guevara commented Nov 13, 2024

Curiously recurring template pattern - spirits away



https://ift.tt/I2D8lQV






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到这个继承文法的时候,子类的类型已经注册,因此这样的继承关系是可行的。

这样的代码看上去蛋疼无比,但是这样的代码演变为一种模式,一定有其特别的地方。所以下文我们就来探究一下其特别之处。

在C++中,虚函数是利用虚函数表来实现的。当调用一个虚函数时,首先获得虚表指针,然后在虚函数表中查找对应的函数指针,最后根据得到的函数指针来进行调用。完整的执行流程中,涉及到多次指针跳转,所以虚函数的开销还是比较大的。而利用CRTP这种模板继承来实现虚函数则可以避免这种查表的负担,其事例代码如下:

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

};

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

这里使用的主要机制就是强制类型转换,如果不涉及到虚继承,强制类型转换基本可以在编译期完成。

有时情况下,我们需要知道特定类型的活跃对象数目。类似于引用计数,只不过是针对类型而言,而不是针对特定对象。通过继承一个counter类,来存储其活跃对象数和总创建对象数。

template <typename T>
struct counter
{
    static int objects_created;
    static int objects_alive;
<span>counter</span><span>()</span>
<span>{</span>
    <span>++</span><span>objects_created</span><span>;</span>
    <span>++</span><span>objects_alive</span><span>;</span>
<span>}</span>

<span>counter</span><span>(</span><span>const</span> <span>counter</span><span>&amp;</span><span>)</span>
<span>{</span>
    <span>++</span><span>objects_created</span><span>;</span>
    <span>++</span><span>objects_alive</span><span>;</span>
<span>}</span>

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的创建销毁的统计信息。

上面的那个例子是针对特定类型的对象创建和销毁计数,事实上我们还可以将单一对象的引用计数也实现。其实也不是完整实现,而只是通过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;
<span>shared_ptr</span><span>&lt;</span><span>_Ty</span><span>&gt;</span> <span>shared_from_this</span><span>()</span>
    <span>{</span>   <span>// return shared_ptr</span>
    <span>return</span> <span>(</span><span>shared_ptr</span><span>&lt;</span><span>_Ty</span><span>&gt;</span><span>(</span><span>_Wptr</span><span>));</span>
    <span>}</span>

};

这样,我们就可以从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,是为了防止自引用。自引用的情况下,对象无法析构,从而导致资源泄漏。

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(){}

<span>void</span>  <span>Alert</span><span>()</span>
<span>{</span>
    <span>AtlMessageBox</span><span>(</span> <span>NULL</span><span>,</span> <span>_T</span><span>(</span><span>"Alert"</span><span>)</span> <span>);</span>
<span>}</span>

};

如果有类CSomeDerive继承 CNoDerive,则无法构造。CSomeDerive的构造函数调用过程如下:由于CFobidDeriveProvider是从CFobidDeriveProviderBase虚拟派生,在虚继承出现的继承层次中,总是在构造非虚基类之前构造虚基类,因而会跳过CNoDerive和CFobidDeriveProvider的构造函数而直接调用CFobidDeriveProviderBase的构造函数,但CSomeDerive不是CFobidDeriveProviderBase的友元,因此也无法调用

CFobidDeriveProviderBase的私有构造函数.故而编译错误。







via spiritsaway.info https://ift.tt/xtvPQyO

November 13, 2024 at 09:46AM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant