• 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的缺点:使用模板类,导致维护复杂度增加,另外,编译期展开类,必定会导致代码会增加很多。

    作者:kaizen
    声明:本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此声明,且在文章明显位置给出本文链接,否则保留追究法律责任的权利。
    签名:顺序 选择 循环
  • 相关阅读:
    函数传参-修改文本
    函数传参-商品计价
    嵌套选项卡自动播放
    仿淘宝自动播放菜单栏
    仿淘宝侧边栏菜单
    图片自动切换
    定时器应用-页面弹出广告
    转:Java面试题集(1-50)
    转:115个Java面试题和答案——终极列表(上)
    毕向东day01笔记--dos-jdk-jre-环境变量等
  • 原文地址:https://www.cnblogs.com/Braveliu/p/15776744.html
Copyright © 2020-2023  润新知