C++ CRTP

CRTP

英: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本身作为模板实参的模板类实例化中派生而来。

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

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

应用示例:

template
class Base
{ };

template
class Derived : public Base>
{ };

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

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

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

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

实例代码如下:

#include
using namespace std;

template
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(this);
    }
};

class A : public IComparable
{
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
{
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
{
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
请按任意键继续. . .
*/

实例代码如下:

#include
using namespace std;

template
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
int Counter::Count = 0;

class A : public Counter
{ };

class B : public Counter
{ };

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

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

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

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

    cout << "B : " << Counter::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
请按任意键继续. . .
*/

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

#include
#include

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

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

struct Empty
{};

int main()
{
    // 正确的示例:两个 shared_ptr 对象将会共享同一对象
    std::shared_ptr gp1 = std::make_shared();
    std::shared_ptr 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 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 bp1 = std::make_shared();
    std::shared_ptr 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 ep1 = std::make_shared();
    std::shared_ptr 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技术是最好的选择。

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

示例代码如下:

#include
#include
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实现支持。

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

#include
#include

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

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

class Square : public Shape
{
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
#include

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

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

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

template
void calc(Shape& 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
*/

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

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

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

Original: https://www.cnblogs.com/Braveliu/p/15776744.html
Author: kaizen
Title: C++ CRTP

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/535260/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • C++实现图的遍历和最短路径

    摘自:https://blog.csdn.net/qq_45694646/article/details/106764026 C++实现图的基本操作 数据结构之图(存储,遍历,最短…

    C++ 2023年5月29日
    080
  • C++多线程库的常用函数积累和整理

    std::scoped_lock 待完成 标准库中 std::recursive_mutex提供这样的功能 一个互斥量可以在同一线程上多次上锁, 待完成 std::thread 类…

    C++ 2023年5月29日
    067
  • C++矩阵运算库推荐

    最近在几个地方都看到有人问C++下用什么矩阵运算库比较好,顺便做了个调查,做一些相关的推荐吧。主要针对稠密矩阵,有时间会再写一个稀疏矩阵的推荐。 Armadillo:C++下的Ma…

    C++ 2023年5月29日
    079
  • C++ register 关键字

    register 简介: register 就像是汉语和英语中的形容词(不要问为什么只有两种语言,因为小编只会这两种),在 C++ 中 register 是用来修饰变量的。 reg…

    C++ 2023年5月29日
    063
  • 设计模式C++实现——工厂模式

    软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径。设计模式中运用了面向对象编程语言的重要特性:封装、继承、多态,真正领悟设计模式的精髓是可能一个漫长的过程,需要大…

    C++ 2023年5月29日
    059
  • Prim算法(二)之 C++详解

    普里姆(Prim)算法,是用来求加权连通图的最小生成树的算法。 基本思想对于图G而言,V是所有顶点的集合;现在,设置两个新的集合U和T,其中U用于存放G的最小生成树中的顶点,T存放…

    C++ 2023年5月29日
    071
  • C++11新特性学习

    http://www.cprogramming.com/c++11/c++11-lambda-closures.html Original: https://www.cnblogs…

    C++ 2023年5月29日
    063
  • EclipseC++学习笔记-8 兼容vim

    1、搜索vim2、点击install 本博客是个人工作中记录,遇到问题可以互相探讨,没有遇到的问题可能没有时间去特意研究,勿扰。另外建了几个QQ技术群:2、全栈技术群:616945…

    C++ 2023年5月29日
    060
  • 自己总结 C++ 代码规范

    1.编写原则,代码尽量简单,简洁,高效,自己写的代码让自己和别人容易看懂 2.命名: a. 类的成员变量加前缀 m_(表示 member)。 常量全用大写的字母,用下划线分割单词(…

    C++ 2023年5月29日
    070
  • VS Code C++ 代码格式化方法(clang-format)

    转自:https://blog.csdn.net/core571/article/details/82867932?depth_1-utm_source=distribute.pc…

    C++ 2023年5月29日
    0126
  • 客户端单元测试实践——C++篇

    作者 | 思兼来源 | 阿里开发者公众号 背景 我们团队在手淘中主要负责BehaviX模块,代码主要是一些逻辑功能,很少涉及到UI,为了减少双端不一致问题、提高性能,我们采用了将核…

    C++ 2023年5月29日
    080
  • C/C++定义全局变量/常量几种方法的区别

    在讨论全局变量之前我们先要明白几个基本的概念: 编译单元(模块):在IDE开发工具大行其道的今天,对于编译的一些概念很多人已经不再清楚了,很多程序员最怕的就是处理连接错误(LINK…

    C++ 2023年5月29日
    084
  • EclipseC++学习笔记-9 将文件从项目中排除与恢复

    选中文件,取消Debug,Release勾选如果需要恢复,勾选Debug,Release即可。这样可以方便进行多个带main源码测试。 本博客是个人工作中记录,遇到问题可以互相探讨…

    C++ 2023年5月29日
    081
  • visual studio code的c++扩展

    posted @2020-08-20 10:07 kissrule 阅读(338 ) 评论() 编辑 Original: https://www.cnblogs.com/longc…

    C++ 2023年5月29日
    0116
  • C++ 为什么不加入垃圾回收机制

    来源:http://www.codeceo.com/article/why-cpp-not-use-gc.html作者:M-先生 Java的爱好者们经常批评C++中没有提供与Jav…

    C++ 2023年5月29日
    057
  • (筆記) 如何讀取binary file某個byte的值? (C/C++) (C)

    Abstract通常公司為了保護其智慧財產權,會自己定義檔案格式,其header區會定義每個byte各代表某項資訊,所以常常需要直接對binary檔的某byte直接進行讀取。 In…

    C++ 2023年5月29日
    067
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球