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++实现HashMap

    算法思想: 哈希表 什么是哈希表 在前面讨论的各种结构(线性表、树等)中,记录在结构中的相对位置是随机的,和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和…

    C++ 2023年5月29日
    091
  • C++11 并发指南五(std::condition_variable 详解)

    std::condition_variable 是条件变量,更多有关条件变量的定义参考维基百科。Linux 下使用 Pthread 库中的 pthread_cond_*() 函数提…

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

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

    C++ 2023年5月29日
    063
  • VSCode C++使用-1 快速创建C++、C项目

    1、安装C/C++ Project Generator2、Ctrl+Shift+P 选择Create C++ Project3、生成一个支持windows,linux多个平台的项目…

    C++ 2023年5月29日
    062
  • [C++]assert的加强版——Ensure的简易实现

    Ensure用法如: ENSURE(0 断言失败时,会打印: Failed: 0 概括来说,Ensure至少包括以下特性: 1) 不区分debug和release,始终生效。 2)…

    C++ 2023年5月29日
    067
  • Guide into OpenMP: Easy multithreading programming for C++

    The for construct splits the for-loop so that each thread in the current team handles a di…

    C++ 2023年5月29日
    080
  • 2.设计模式-七大原则(C++)

    任何一个设计模式都离不开七大原则中的任一原则.所以七大原则非常重要,只要我们明白了七大原则,就可以知道如何在项目中使用什么设计原则了. 设计模式的七大原则如下所示: 1.单一职责原…

    C++ 2023年5月29日
    065
  • C/C++中static,const,inline三种关键字的总结(参照网络)

    一、 关于staticstatic 是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 修饰符的产生原因、作用谈起,全面分析static 修饰符…

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

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

    C++ 2023年5月29日
    0116
  • C++ 11 关键字:thread_local(转)

    thread_local 是 C++ 11 新引入的一种存储类型,它会影响变量的存储周期。 C++ 中有 4 种存储周期: 有且只有 thread_local 关键字修饰的变量具有…

    C++ 2023年5月29日
    052
  • 彻底搞懂之C++智能指针

    前言 在现代 c + + 编程中,标准库包含 智能指针,这些指针用于帮助确保程序不会出现内存和资源泄漏,并具有异常安全。 标准库智能指针分类 auto_ptr, shared_pt…

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

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

    C++ 2023年5月29日
    067
  • C#与C/C++的交互

    C#与C/C++的交互 最近在编写Warensoft3D游戏引擎,并预计明年年初发布测试版本,底层引擎使用DirectX和MONO来编写,上层的逻辑使用C#来编写,因此编写了大量C…

    C++ 2023年5月29日
    076
  • 玩转cocos2d-x lua-binding, 实现c++与lua混合编程

    引言 城市精灵GO(http://csjl.teamtop3.com/)是一款基于cocos2d-x开发的LBS社交游戏, 通过真实地图的探索, 发现和抓捕隐匿于身边的野生精灵, …

    C++ 2023年5月29日
    063
  • c和c++开发工具之clion和vs

    个人体验结果 如果是CMake或者要跨平台的话,建议使用CLion 像我在看书写练习题的话,Clion使用cmake编译c/c++源码更简单上手使用。 如果项目不大,两者都可以。如…

    C++ 2023年5月29日
    0142
  • c++的对象初始化

    忍不住了,不得不吐槽一下,妈的,太复杂了,真难,搞得太复杂了,看不懂,看不懂,真的越来越复杂了,没有必要啊! 看得了头皮发麻,搞不明白,咱又不是干编译器的,投降了。 工程代码中,代…

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