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)

大家都在看

  • Android jni c/c++线程通过CallVoidMethod调用java函数出现奔溃问题

    最近在移植网络摄像机里的p2p库到android平台,需要用到jni,最近在c线程了调用java函数的时候出现一个问题,假如在同一个线程调用java函数是没问题的,但在一个c线程了…

    C++ 2023年5月29日
    035
  • C++入门笔记

    一直对C++感到很恐惧,大学里有C的基础,今天终于鼓足勇气入门C++,先大致了解一下,以后用到的时候再详细深入。 Android中有一些很火的领域比如:音视频、物联网,都会涉及到J…

    C++ 2023年5月29日
    072
  • 当C++遇到iOS应用开发之—List集合

    在Object-c中,数组使用NSArray和NSMutableArray(可变长数组)。使用语法如下: NSArray *array = [[NSArray alloc] ini…

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

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

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

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

    C++ 2023年5月29日
    074
  • 当C++遇到iOS应用开发—Dict集合

    在Object-c中,字典(KEY/VALUE)使用NSDictionary 和NSMutableDictionary(可变长)。使用语法如下: NSDictionary *dic…

    C++ 2023年5月29日
    071
  • C++ Addon Async 异步机制

    线程队列: libuv,window 可在libuv官网下载相应版本 opencv: 编译的时候opencv的位数要和 node的bit 一致 兼容electron : node-…

    C++ 2023年5月29日
    053
  • C++11:lambda表达式详细介绍

    优点如下: 声明式编程风格:就地匿名定义目标函数或函数对象,有更好的可读性和可维护性。 简洁:不需要额外写一个命名函数或函数对象,,避免了代码膨胀和功能分散。 更加灵活:在需要的时…

    C++ 2023年5月29日
    053
  • 347. 前 K 个高频元素 (优先队列 c++)

    【C++】优先队列的预备知识 对于我这种初学者来说,一个优先队列就给我搞蒙了,在此记录一下,造福后来人 定义 priority_queue<type, container, …

    C++ 2023年5月29日
    059
  • error: Microsoft Visual C++ 14.0 is required问题最佳解决方法

    对于程序员来说,经常pip安装自己所需要的包,大部分的包基本都能安装,但是总会遇到包安装不了的问题,预研学习的动力第一步就被安装包给扼杀了。其中最受困扰的就是这个问题:error:…

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

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

    C++ 2023年5月29日
    062
  • [C++]一份Linq to object的C++实现

    几个月的构想+0.5小时的设计+4小时的linq.h编码+3小时的测试编码。 大量使用C++11的特性,在GCC 4.7.2下编译通过。 关于实现相关的描述就不说了,我表达能力差,…

    C++ 2023年5月29日
    052
  • 从三个语言(C++,Java,C#)的几个性能测试案例来看性能优化

    随着时间的发展,现在的虚拟机技术越来越成熟了,在有些情况下,Java,.Net等虚拟机密集计算的性能已经和C++相仿,在个别情况下,甚至还要更加优秀。本文详细分析几个性能测试案例,…

    C++ 2023年5月29日
    082
  • (筆記) 如何寫入binary file某個byte連續n byte的值? (C/C++) (C)

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

    C++ 2023年5月29日
    071
  • [C++] inline内联函数使用方法

    C++支持内联函数,目的是为了提高函数的执行效率,类似于C语言中的宏定义 内联函数在调用时将它在程序中的每个调用点展开,不用额外分配栈空间 内联函数的定义在一个源文件中出现一次,但…

    C++ 2023年5月29日
    084
  • 关于C++单件模式释放对象

    最近接触的一个项目要用到单件模式,我像往常一样哒哒(敲击键盘ing)一个单件模式的典型结构很快就出现在我的面前: 不知道为什么,这次突然觉得new这个单词太耀眼了,熟悉c++的程序…

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