C++代碼模板之CRTP

更多精彩內容,請關注微信公衆號:後端技術小屋c++

本文將介紹一下c++代碼模板的小技巧 ----- CRTP數據庫

虛函數

在介紹 CRTP 以前,咱們先來了解下虛函數。編程

虛函數是經過指向派生類的基類指針或引用,訪問派生類中同名覆蓋成員函數,從而實現了多態的特性。後端

一段簡單的代碼示例微信

class A
{
public:
    virtual void print()
    {
        std::cout << "Hello from A" << std::endl;
    }
};

class B : public A
{
public:
    void print() override
    {
        std::cout << "Hello from B" << std::endl;
    }
};

虛函數實現了多態的特性,可是每次調用的時候都要對虛函數表進行 look-up, 因此開銷不低,較之直接調用具體對象的方法,虛函數調用一般會慢一個數量級以上。在一些對性能敏感領域的軟件系統中,好比OLAP數據庫系統,須要對海量數據進行計算分析,虛函數的調用將會放大特別嚴重。ide

CRTP

奇異遞歸模板模式(Curiously Recurring Template Pattern,CRTP),CRTP是C++模板編程時的一種常見技巧(idiom):把派生類做爲基類的模板參數。更通常地被稱做F-bound polymorphism,是一類F 界量化。函數

  • CRTP 的基本範式
template <typename T>
class Base
{
    ...
};

class Derived : public Base<Derived>
{
    ...
};

這樣作的目的在於在基類中使用派生類的方法,從基類的角度來看,派生類也是一個基類,基類能夠經過static_cast將其轉爲派生類,從而靜態使用派生類的成員和方法,以下:源碼分析

template <typename T>
class Base
{
public:
    void doWhat()
    {
        T& derived = static_cast<T&>(*this);
        // use derived...
    }
};
  • 靜態動態

Andrei Alexandrescu在Modern C++ Design中稱 CRTP 爲靜態多態(static polymorphism)。性能

相比於普通繼承方式實現的多臺,CRTP能夠在編譯器實現類型的綁定,這種方式實現了虛函數的效果,同時也避免了動態多態的代價。this

  • 權限控制

爲了讓基類能訪問派生類的私有成員或方法,咱們能夠在派生類中和基類成爲友元類。

friend class Base<Derived>;
  • std::enable_shared_from_this

假如在c++中想要在一個已被shareptr管理的類型對象內獲取並返回this,爲了防止被管理的對象已被智能指針釋放,而致使this成爲懸空指針,可能會考慮以share_ptr的形式返回this指針,咱們可使用 std::enable_shared_from_this, 它自己就是一種CRTP在標準庫中的實現

struct FOO: std::enable_shared_from_this<FOO>
{
    std::shared_ptr<FOO> getptr() {
        return shared_from_this();
    }
};
  • CRTP 示例 (來自clickhouse源碼)
/// Implement method to obtain an address of 'add' function.
template <typename Derived>
class IAggregateFunctionHelper : public IAggregateFunction
{
private:
    static void addFree(const IAggregateFunction * that, AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena * arena)
    {
        static_cast<const Derived &>(*that).add(place, columns, row_num, arena);
    }

public:
    IAggregateFunctionHelper(const DataTypes & argument_types_, const Array & parameters_)
        : IAggregateFunction(argument_types_, parameters_) {}

    AddFunc getAddressOfAddFunction() const override { return &addFree; }

    void addBatch(size_t batch_size, AggregateDataPtr * places, size_t place_offset, const IColumn ** columns, Arena * arena) const override
    {
        for (size_t i = 0; i < batch_size; ++i)
            static_cast<const Derived *>(this)->add(places[i] + place_offset, columns, i, arena);
    }

    void addBatchSinglePlace(size_t batch_size, AggregateDataPtr place, const IColumn ** columns, Arena * arena) const override
    {
        for (size_t i = 0; i < batch_size; ++i)
            static_cast<const Derived *>(this)->add(place, columns, i, arena);
    }

    void addBatchArray(
        size_t batch_size, AggregateDataPtr * places, size_t place_offset, const IColumn ** columns, const UInt64 * offsets, Arena * arena)
        const override
    {
        size_t current_offset = 0;
        for (size_t i = 0; i < batch_size; ++i)
        {
            size_t next_offset = offsets[i];
            for (size_t j = current_offset; j < next_offset; ++j)
                static_cast<const Derived *>(this)->add(places[i] + place_offset, columns, j, arena);
            current_offset = next_offset;
        }
    }
};

總結

若是想在編譯期肯定經過基類來獲得派生類的行爲,CRTP即是一種絕佳的選擇, 😃

推薦閱讀

更多精彩內容,請掃碼關注微信公衆號:後端技術小屋。若是以爲文章對你有幫助的話,請多多分享、轉發、在看。
二維碼

相關文章
相關標籤/搜索