C++ CRTP

CRTP

1 CRTP

1.1 定义

英:The curiously recurring template pattern (CRTP) is a C++ idiom in which a class X derives from a class template instantiation using X itself as template argument.

中:奇异递归模板模式是一种C++习惯用法,在这种模式中,类X由使用X本身作为模板实参的模板类实例化中派生而来。

1.2 简介

CRPT,奇异递归模板模式,一种特殊的模板技术使用方式。

当一个基类是模板类时,其派生类再将自身作为此基类的模板实参实例化后派生而来的类。

应用示例:

template <typename T>
class Base
{ };

template <typename T>
class Derived : public Base<Derived<T>>
{ };

通过示例可知:Base作为基类,是一个模板类;Derived作为派生类,也是个模板类;将Derived作为Base基类的实参,即所谓递归模板模式。

之所以“奇异”,因为怎么能把一个对于基类来说未知的类型传给基类呢?但在这里的确是可以的。

因为基类是模板类,我们传递给基类的是一种类型(不是数据),只要不在基类创建T类型对象,就不会出现类自我包含的问题。

当然,一般这种应用只在基类中实现一些与派生类有关的方法,让派生类继承后获得一些相应功能。

2 概念示例

2.1 IComparable

实例代码如下:

#include<iostream>
using namespace std;

template <typename T>
class IComparable
{
public:
    bool less(const T& b)
    {
        return self()->lessImpl(b);
    }

protected:
    bool lessImpl(const T& b)   // Need Derived to override lessImpl().
    {
        cout << "call IComparable::lessImpl" << endl;
        return true;
    }

private:
    T* self()
    {
        return static_cast<T*>(this);
    }
};

class A : public IComparable<A>
{
public:
    A(int num) : N(num)
    { }

    bool lessImpl(const A& b)
    {
        cout << "call A::lessImpl" << endl;
        return N < b.N;
    }

public:
    int N;
};

class B : public IComparable<B>
{
public:
    B(int num1, int num2) : N1(num1), N2(num2)
    { }

    bool lessImpl(const B & b)
    {
        cout << "call B::lessImpl" << endl;
        return N1 < b.N1 || N2 < b.N2;
    }

private:
    int N1, N2;
};

class C : public IComparable<C>
{
public:
    C() {}
};

int main()
{
    A a(15), b(10);
    cout << a.less(b) << endl; // 0

    B c(5, 10), d(5, 0);
    cout << c.less(d) << endl; // 0

    C e, f;
    cout << e.less(f) << endl; // 1

    system("pause");
}

/* result
call A::lessImpl
0
call B::lessImpl
0
call IComparable::lessImpl
1
请按任意键继续. . .
*/

2.2 Counter

实例代码如下:

#include <iostream>
using namespace std;

template <typename T>
class Counter
{
public:
    static size_t get()
    {
        return Count;
    }

    Counter()
    {
        cout << "call Counter T : " << typeid(T).name() << endl;
        add(1);
    }

    Counter(const Counter& other)
    {
        cout << "call const Counter &  T : " << typeid(T).name() << endl;
        add(1);
    }

    ~Counter()
    {
        cout << "call ~Counter T : " << typeid(T).name() << endl;
        add(-1);
    }

private:
    static int Count;
    static void add(int n)
    {
        Count += n;
    }
};

template <typename T>
int Counter<T>::Count = 0;

class A : public Counter<A>
{ };

class B : public Counter<B>
{ };

int main()
{
    A a1;
    cout << "A : " << Counter<A>::get() << endl;   // 1

    {
        B b1;
        cout << "B : " << Counter<B>::get() << endl;  // 1

        {
            A a2;
            cout << "A : " << Counter<A>::get() << endl;  // 2

            A a3(a2);
            cout << "A : " << Counter<A>::get() << endl;  // 3
        }
        cout << "A : " << Counter<A>::get() << endl;  // 1
    }

    cout << "B : " << Counter<B>::get() << endl;  // 0

    system("pause");
}

/* result
call Counter T : class A
A : 1
call Counter T : class B
B : 1
call Counter T : class A
A : 2
call const Counter &  T : class A
A : 3
call ~Counter T : class A
call ~Counter T : class A
A : 1
call ~Counter T : class B
B : 0
请按任意键继续. . .
*/

3 应用实例

应用实例来自cppreference官网(稍作更改),代码如下:

#include <memory>
#include <iostream>

struct Good : std::enable_shared_from_this<Good> // 注意:继承
{
    std::shared_ptr<Good> getptr()
    {
        return shared_from_this();
    }
};

struct Bad
{
    // 错误写法:用不安全的表达式试图获得 this 的 shared_ptr 对象
    std::shared_ptr<Bad> getptr()
    {
        return std::shared_ptr<Bad>(this);
    }
    ~Bad()
    { 
        std::cout << "Bad::~Bad() called\n";
    }
};

struct Empty
{};

int main()
{
    // 正确的示例:两个 shared_ptr 对象将会共享同一对象
    std::shared_ptr<Good> gp1 = std::make_shared<Good>();
    std::shared_ptr<Good> gp2 = gp1->getptr();
    std::cout << "gp1.use_count() = " << gp1.use_count() << '\n';  // gp1.use_count() = 2
    std::cout << "gp2.use_count() = " << gp2.use_count() << '\n';  // gp2.use_count() = 2

    // 错误的使用示例:调用 shared_from_this 但其没有被 std::shared_ptr 占有
    try
    {
        Good not_so_good;
        std::shared_ptr<Good> gp1 = not_so_good.getptr();
    }
    catch (std::bad_weak_ptr& e)
    {
        // C++17 前为未定义行为; C++17 起抛出 std::bad_weak_ptr 异常
        std::cout << e.what() << '\n';
    }

    // 错误的示例,每个 shared_ptr 都认为自己是对象仅有的所有者
    std::shared_ptr<Bad> bp1 = std::make_shared<Bad>();
    std::shared_ptr<Bad> bp2 = bp1->getptr();
    std::cout << "bp1.use_count() = " << bp1.use_count() << '\n'; // bp1.use_count() = 1
    std::cout << "bp2.use_count() = " << bp2.use_count() << '\n'; // bp2.use_count() = 1

    std::shared_ptr<Empty> ep1 = std::make_shared<Empty>();
    std::shared_ptr<Empty> ep2(ep1.get());
    Empty* ep3 = ep1.get();
    std::cout << "ep1.use_count() = " << ep1.use_count() << '\n';  // ep1.use_count() = 1
    std::cout << "ep2.use_count() = " << ep2.use_count() << '\n';  // ep2.use_count() = 1

    system("pause");
}

/* result:
gp1.use_count() = 2
gp2.use_count() = 2
bad_weak_ptr
bp1.use_count() = 1
bp2.use_count() = 1
ep1.use_count() = 1
ep2.use_count() = 1
*/

在C++标准库中,最最经典的是enable_shared_from_this

为了实现从类中传出一个安全的shared_ptr来包装this指针,并且只能对现有的类做极小修改,对实际使用没有影响。

显然,使用CRTP技术是最好的选择。

4 运行时(动态)多态与编译期(静态)多态

为了便于理解动态多态和静态多态,请看下面示例。

4.1 运行时多态

示例代码如下:

#include <iostream>
#include <string>
using namespace std;

class Shape
{
public:
    virtual void calc_area() = 0;
};

class Circle : public Shape
{
public:
    virtual void calc_area() { cout << "call Circle::calc_area" << endl; }
};

class Square : public Shape
{
public:
    virtual void calc_area() { cout << "call Square::calc_area" << endl; }
};

int main()
{
    Shape* pC = new Circle;
    pC->calc_area(); //call Circle::calc_area
    delete pC;

    Shape* pS = new Square;
    pS->calc_area(); //call Square::calc_area
    delete pS;

    system("pause");
}

不做赘述,因为C++运行时多态利用虚函数virtual实现支持。

4.2 编译期多态

如上示例,改为编译期多态,示例代码如下:

#include <iostream>
#include <string>

template <typename T>
class Shape
{
public:
    void calc_area() { static_cast<T*>(this)->do_calc(); };
};

class Circle : public Shape<Circle>
{
public:
    void do_calc() { std::cout << "call Circle::calc_area" << std::endl; }
};

class Square : public Shape<Square>
{
public:
    void do_calc() { std::cout << "call Square::calc_area" << std::endl; }
};

int main()
{
    Circle objC;
    objC.calc_area();  // call Circle::calc_area
    
    Square objS;
    objS.calc_area();  // call Square::calc_area

    system("pause");
}

/* result:
call Circle::calc_area
call Square::calc_area
*/

此编译期多态即CRTP的原型,当然,如果觉得这样不好理解(多态表现不明显),可以再调整一下,如下示例:

#include <iostream>
#include <string>

template <typename T>
class Shape
{
public:
    void calc_area() { static_cast<T*>(this)->do_calc(); };
};

class Circle : public Shape<Circle>
{
public:
    void do_calc() { std::cout << "call Circle::calc_area" << std::endl; }
};

class Square : public Shape<Square>
{
public:
    void do_calc() { std::cout << "call Square::calc_area" << std::endl; }
};

template <typename T> 
void calc(Shape<T>& obj)
{ 
    obj.calc_area();
}

int main()
{
    Circle objC;
    calc(objC);  // call Circle::calc_area
    
    Square objS;
    calc(objS);  // call Square::calc_area

    system("pause");
}

/* result:
call Circle::calc_area
call Square::calc_area
*/

5 CRTP总结

CRTP的应用很广泛,特别很多开源项目都会用到这种技术,经常被用到的场景:

  1. 静态多态
  2. 代码复用
  3. 实例化多套基类静态变量和方法
  4. 实现各个子类实例创建和析构独立计数
  5. enable_shared_from_this

当然,还有个很多其他的应用,根据具体业务场景,具体问题具体分析,因地制宜,活学活用。

从以上示例中,也能明显发现CRTP的缺点:使用模板类,导致维护复杂度增加,另外,编译期展开类,必定会导致代码会增加很多。

posted @ 2022-01-07 20:32  kaizenly  阅读(572)  评论(0编辑  收藏  举报
打赏