奇異遞歸模板模式(CRTP: Curiously Recurring Template Pattern)

學無止境,不斷更新。。。

 

奇異遞歸模板模式(curiously recurring template pattern,CRTP)是C++模板編程時的一種慣用法(idiom):把派生類作爲基類的模板參數。

一般形式

// 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>
{
    // ...
};

靜態多態

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

基類模板利用了其成員函數體(即成員函數的實現)將不被實例化直至聲明很久之後(實際上只有被調用的模板類的成員函數纔會被實例化);並利用了派生類的成員,這是通過{{ilh|lang={{langname|Type conversion}}|lang-code=Type conversion|1=類型轉化|2=類型轉化|d=|nocat=}}。

在上例中,Base<Derived>::interface(),雖然是在struct Derived之前就被聲明瞭,但未被編譯器實例化直至它被實際調用,這發生於Derived聲明之後,此時Derived::implementation()的聲明是已知的。

這種技術獲得了類似於虛函數的效果,並避免了動態多態的代價。也有人把CRTP稱爲“模擬的動態綁定”。

考慮一個基類,沒有虛函數,則它的成員函數能夠調用的其它成員函數,只能是屬於該基類自身。當從這個基類派生其它類時,派生類繼承了所有未被覆蓋(overridden)的基類的數據成員與成員函數。如果派生類調用了一個被繼承的基類的函數,而該函數又調用了其它成員函數,這些成員函數不可能是派生類中的派生或者覆蓋的成員函數。也就是說,基類中是看不到派生類的。但是,基類如果使用了CRTP,則在編譯時派生類的覆蓋的函數可被選中調用。這效果相當於編譯時模擬了虛函數調用但避免了虛函數的尺寸與調用開銷(VTBL結構與方法查找、多繼承機制)等代價。但CRTP的缺點是不能在運行時做出動態綁定

不通過虛函數機制,基類訪問派生類的私有或保護成員,需要把基類聲明爲派生類的友元(friend)。如果一個類有多個基類都出現這種需求,聲明多個基類都是友元會很麻煩。一種解決技巧是在派生類之上再派生一個accessor類,顯然accessor類有權訪問派生類的保護函數;如果基類有權訪問accessor類,就可以間接調用派生類的保護成員了。

不用虛函數機制,通過基類訪問派生類private/protected數據和成員的實現代碼如下

#include <iostream>

template<class DerivedT>
class Base
{
private:
    struct accessor : DerivedT
    {                                      // accessor類沒有數據成員,只有一些靜態成員函數
        static int foo(DerivedT& derived)
        {
            int (DerivedT:: * fn)() = &DerivedT::do_foo; //獲取DerivedT::do_foo的成員函數指針
            return (derived.*fn)();        // 通過成員函數指針的函數調用
        }

        static int bar(DerivedT& derived)
        {
            int (DerivedT:: * fn)() const = &DerivedT::do_bar;  //這裏的const是必須的,因爲DerivedT::do_bar也是const函數
            return (derived.*fn)();
        }
    };                                     // accessor類僅是Base類的成員類型,而沒有實例化爲Base類的數據成員。
public:
    DerivedT& derived()                    // 該成員函數返回派生類的實例的引用
    {
        return static_cast<DerivedT&>(*this);
    }
    int foo()
    {                                       //  該函數具體實現了業務功能
        return accessor::foo(this->derived());
    }

    int bar()
    {
        return accessor::bar(this->derived());
    }
};

struct Derived : Base<Derived>             //  派生類不需要任何特別的友元聲明
{
  protected:
      int do_foo()
      {
          // ... 具體實現
          std::cout << "Derived::do_foo()" << std::endl;
          return 1;
      }

//private:
      int do_bar() const
      {
          std::cout << "Derived::do_bar()" << std::endl;
          return value;
      }
private:
    int value{-1};
};

int main()
{

    Base<Derived> b;
    b.foo();
    b.bar();

    return 0;
}

例子1:對象計數

統計一個類的實例對象創建與析構的數據。使用CRTP很容易可以解決這個問題。

#include <iostream>

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>
{
public:
    static int getCreatedX()
    {
        return objects_created;
    }

    static int getAliveX()
    {
        return objects_alive;
    }
};

class Y : counter<Y>
{
public:
    static int getCreatedX()
    {
        return objects_created;
    }

    static int getAliveX()
    {
        return objects_alive;
    }
};

void foo(int count = 5)
{
    for (int i = 0; i < count; ++i)
    {
        X x;
        Y y;
    }

}

int main()
{
    X x1;
    Y y1;
    {
        X x;
    }
    foo();

    std::cout << "X created: " << x1.getCreatedX() << ", X alived: " << X::getAliveX() << std::endl;

    return 0;
}

例子2:多態複製構造

當使用多態時,常需要基於基類指針創建對象的一份拷貝。常見辦法是增加clone虛函數在每一個派生類中。使用CRTP,可以避免在派生類中增加這樣的虛函數。

#include <iostream>

// Base class has a pure virtual function for cloning
class Shape
{
public:
    virtual ~Shape() = default;
    virtual Shape *clone() const = 0;
};

// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape_CRTP : public Shape
{
public:
    Shape *clone() const override
    {
        return new Derived(static_cast<Derived const&>(*this));
    }
};

// Nice macro which ensures correct CRTP usage
#define Derive_Shape_CRTP(DerivedClass) class DerivedClass: public Shape_CRTP<DerivedClass>

// Every derived class inherits from Shape_CRTP instead of Shape
Derive_Shape_CRTP(Square) {};
Derive_Shape_CRTP(Circle) {};

int main()
{
    Square s;
    auto s1 = s.clone();    //s1是Shape*類型指向Square
    auto s2 = s1->clone();  //s21是Shape*類型指向Square

    return 0;
}

這樣就可以通過shapePtr->clone()的方式,就可以得到Square,Circle等對象的副本了。

例子3:不可派生的類

一個類如果不希望被繼承,C++11中可以使用finall來實現,C++98中可以用虛繼承來實現:

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 var;
    //D d;  //這一行編譯將導致錯誤,因爲D類的默認構造函數不合法
    return 0;
}

如果定義D類型的對象,visual studio2019報錯如下:

error: 'MakeFinally<T>::~MakeFinally() [with T = MyClass]' is private within this context

例子4:std::enable_shared_from_this

C++標準庫頭文件<memory>中,std::shared_ptr類封裝了可被共享使用的指針或資源。一個被共享的對象不能直接把自身的原始指針(raw pointer)this傳遞給std::shared_ptr的容器對象(如一個std::vector),因爲這會生成該被共享的對象的額外的共享指針控制塊。爲此,std::shared_ptr API提供了一種類模板設施std::enable_shared_from_this,包含了成員函數shared_from_this,從而允許從this創建一個std::shared_ptr對象。

#include <iostream>
#include <vector>
#include <memory>

class mySharedClass:public  std::enable_shared_from_this<mySharedClass>
{
public:
    mySharedClass() = default;
    mySharedClass(int value): value{value}
    {
    }
    int getValue() const
    {
        return value;
    }
private:
    int value{-1};
  // ...
};

int main()
{
    std::vector<std::shared_ptr<mySharedClass>> spv;
    spv.push_back(std::make_shared<mySharedClass>());
    std::shared_ptr<mySharedClass> p(new mySharedClass(10));
    mySharedClass &r=*p;//這裏r是p所管理mySharedClass對象的引用別名
    spv.emplace_back(r.shared_from_this());//p是管理的mySharedClass對象的std::shared_ptr指針,所以可以使用shared_from_this

    for (const auto& vi : spv)
    {
      std::cout << "use_count=" << vi.use_count() << ", value=" << vi->getValue() << std::endl;
    }
    return 0;
}

關於enable_shared_from_this請閱讀

本文參考:https://zh.wikipedia.org/wiki/%E5%A5%87%E5%BC%82%E9%80%92%E5%BD%92%E6%A8%A1%E6%9D%BF%E6%A8%A1%E5%BC%8F

發佈了243 篇原創文章 · 獲贊 37 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章