Non-Profit, International

Spirit unsterblich.

C++ CRTP

字数统计:815 blog

奇异递归模板模式(Curiously Recurring Template Pattern)是 C++ 模板的一个技巧。

CRTP 本质是因为 C++ 的类模板和类可以互相继承,这使得类模板的参数化类型可以使用派生类。由于模板是在实例化时才确定的,这使得基类可以知道派生类的类型,从而可以通过基类的接口转发派生类的接口。


template <typename T>
struct Base
{
};

struct Derived : public Base<Derived>
{
};


template <typename T>
struct Base
{
    void interface()
    {
        static_cast<T&>(*this).implementation();
    }
};

struct Derived : public Base<Derived>
{
    void implementation()
    {
        std::cout << "Derived impl" << std::endl;
    }
};

int main()
{
    Derived d;
    d.interface();
}

除了使基类可以调用派生类接口以外,CRTP 还可以通过函数模板的隐式特化实现编译期静态选择:


template <typename T>
struct Base
{
    void interface()
    {
        static_cast<T&>(*this).interface();
    }
};

struct Derived : public Base<Derived>
{
    void interface()
    {
        std::cout << "Derived interface" << std::endl;
    }
};

template<typename T>
void foo(Base<T>& t)
{
    t.interface();
}

int main()
{
    Derived d;
    foo(d);
}

CRTP 的一个广泛的用途是自动生成运算符:二元复合算数运算符(+=-=/= etc.)可以根据二元算术运算符(+-/)实现,那么就可以设计一个基类模板,其中定义复合算数运算符的重载,同时这些重载函数内会调用派生类的算术运算符。这使得派生类只需要继承该基类,并且实现算术运算符,就可以自动获得复合算术运算符。

同理,不等运算也可也从相等运算生成,但 C++20 开始编译器已经可以自动执行此过程,所以 C++20 起不需要该种方式。

std::enable_shared_from_this 就是对 CRTP 的一个经典使用:

std::enable_shared_from_this 能让其一个对象(假设其名为 t ,且已被一个 std::shared_ptr 对象 pt 管理)安全地生成其他额外的 std::shared_ptr 实例(假设名为 pt1pt2,… ) ,它们与 pt 共享对象 t 的所有权。

若一个类 T 继承 std::enable_shared_from_this<T>,则会为该类 T 提供成员函数 shared_from_this。当 T 类型对象 t 被一个为名为 ptstd::shared_ptr<T> 类对象管理时,调用 T::shared_from_this,将会返回一个新的 std::shared_ptr<T>,它与 pt 共享 t 的所有权。

其内部保存着一个对 this 的弱引用(例如 std::weak_ptr )。 std::shared_ptr 的构造函数检测无歧义且可访问的 (C++17 起) enable_shared_from_this 基类,并且若内部存储的弱引用未为生存的 std::shared_ptr 占有,则 (C++17 起) 赋值新建的 std::shared_ptr 为内部存储的弱引用。为已为另一 std::shared_ptr 所管理的对象构造一个 std::shared_ptr ,将不会考虑内部存储的弱引用,从而将导致未定义行为。

参考:
C++ Templates 第二版 21.2 std::enable_shared_from_this

若无特殊声明,本人原创文章以 CC BY-SA 3.0 许可协议 提供。