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即是一种绝佳的选择, 😃

推荐阅读

更多精彩内容,请扫码关注微信公众号:后端技术小屋。若是以为文章对你有帮助的话,请多多分享、转发、在看。
二维码

相关文章
相关标签/搜索