C++设计模式之CRTP的使用

 更新时间:2023年10月18日 14:09:34   作者:思想觉悟  
CRTP全称是curious recurring template pattern,即奇异递归模版模式,是一种c++的设计模式,精巧地结合了继承和模板编程的技术,下面就跟随小编一起来学习一下CRTP的使用吧

什么是CRTP

CRTP全称是curious recurring template pattern,即奇异递归模版模式,是一种c++的设计模式,精巧地结合了继承和模板编程的技术。可以用来给c++的class提供额外功能、实现静态多态等。

在CRTP之前只听说C++通过指针实现了动态多态,现在居然搞出了一个静态多态的东西出来?不得不感慨C++真是一门高深莫测的语言...

动态多态

在了解静态多态之前我们先来回顾一下动态多态,C++ 通过类的继承与虚函数的动态绑定,实现了多态。这种特性,使得我们能够用基类的指针,访问子类的实例。

#include <iostream>

class Animal{
public:
    // 注意需要添加virtual关键字
    virtual void run(){
        std::cout << "Animal run" << std::endl;
    }
};

class Cat:public Animal{
public:
    void run() override{
        std::cout << "Cat run" << std::endl;
    }
};

int main() {
    std::vector<Animal> animalVec;
    animalVec.emplace_back(Animal());
    // 非指针的形式,其实内部调用的还是Animal的run
    animalVec.emplace_back(Cat());
    for (auto animal:animalVec) {
        animal.run();
    }
    // 动态多态需要通过指针的形式实现
    std::vector<Animal*> animalVecPtr;
    animalVecPtr.push_back(new Animal());
    animalVecPtr.push_back(new Cat());
    for (auto animal:animalVecPtr) {
        animal->run();
    }
    return 0;
}

CRTP的一个重要功能就是用来实现静态多态,CRTP在编译阶段就将子类类型以模版的形式传递到父类,以便在编译阶段实现多态性,这就是静态多态。

既然有了动态多态,为什么还需要静态多态呢?答案是精益求精,为了效率而生...

我们知道动态多态是基于虚函数的形式在运行时进行动态绑定的,因此每次运行时都需要查询虚函数表,所以动态绑定会降低程序的执行效率。 为了兼顾多态与效率,就提出了CRTP。

CRTP的使用

我们先来看看在cppreference中是如何使用CRTP的

下面我们依然使用上面Animal的例子通过CRTP的方式实现静态多态。

首先我们按照官方的例子,依瓢画葫芦:

#include <iostream>

template  < class T >
class Animal{
public:
    virtual ~Animal(){

    };
    // CRTP这里已经不需要使用virtual关键字了
     void run(){
        (static_cast<T*>(this))->run();
    }
};

class Cat:public Animal<Cat>{
public:
    void run(){
        std::cout << "Cat run" << std::endl;
    }
};

class Dog:public Animal<Dog>{
public:
    void run(){
        std::cout << "Dog run" << std::endl;
    }
};

int main() {
    Animal<Cat>* cat = new Cat;
    cat->run();
    delete cat;
    Animal<Dog>* dog = new Dog;
    dog->run();
    delete dog;
    return 0;
}

程序运行起来后打印如下:

可以发现通过CRTP我们不使用关键字virtual也能实现了通过父类指针调用子类方法效果,这就是静态多态的优点,它比动态多态更高效,更安全。

通过上面的例子我们总结一下使用CRTP的三个重要步骤:

  • 继承自模版类,因为用到了继承,因此析构函数需要用virtual修饰,以避免内存泄露。
  • 子类将自身通过模板参数传递给父类。
  • 父类通过static_cast关键字将模板参数静态转化成子类,然后调用子类的鸭子模型方法。

一般来说将父类转换成子类一般使用的是dynamic_cast,而CRTP是在编译期间就已经明确知道了子类的具体类型,因此直接使用static_cast更为高效。 这也正是CRTP这种设计的一大精髓。

通过仔细对比我们动态多态和静态多态的两个例子我们发现还是有点不一样的,我们在动态多态中将Animal的指针添加到了std::vector中去,那么我们的CRTP能否也这样做呢? 我们来试一下:

#include <iostream>

template<class T>
class Animal {
public:
    virtual ~Animal() {

    };
    // CRTP这里已经不需要使用virtual关键字了
    void run() {
        (static_cast<T *>(this))->run();
    }
};

class Cat : public Animal<Cat> {
public:
    void run() {
        std::cout << "Cat run" << std::endl;
    }
};

class Dog : public Animal<Dog> {
public:
    void run() {
        std::cout << "Dog run" << std::endl;
    }
};

int main() {
    std::vector<Animal<Cat>*> animalVec;
    animalVec.emplace_back(new Cat());
    // 报错了,因为vector存放的数据类型是Animal<Cat>
    animalVec.emplace_back(new Dog());
    for (auto animal: animalVec) {
        animal->run();
    }
    return 0;
}

我们发现报错了,因为Animal和Animal不是同样的数据类型,不能同时放入同一个vector中去。 既然问题的根源是他们不是同样的数据类型,那么我们将它们变成同样的数据类型不就是行了吗?那么怎么把它们变成同样的数据类型呢?

让它们继承一个共同的基类即可。这样就是动态多态与静态多态结合使用的例子了。

实例代码如下:

#include <iostream>

class BaseAnimal {
public:
    virtual ~BaseAnimal() {

    };

    virtual void run() = 0;
};

template<class T>
class Animal: public BaseAnimal{
public:
    virtual ~Animal() {

    };
    // CRTP这里已经不需要使用virtual关键字了
    void run() override{
        (static_cast<T *>(this))->run();
    }
};

class Cat : public Animal<Cat> {
public:
    void run() override {
        std::cout << "Cat run" << std::endl;
    }
};

class Dog : public Animal<Dog> {
public:
    void run() override {
        std::cout << "Dog run" << std::endl;
    }
};

int main() {
    std::vector<BaseAnimal*> animalVec;
    animalVec.emplace_back(new Cat());
    // 报错了,因为vector存放的数据类型是Animal<Cat>
    animalVec.emplace_back(new Dog());
    for (auto animal: animalVec) {
        animal->run();
    }
    return 0;
}

这样一来,我们通过CRTP与虚函数结合,即保留了动态多态的各种特性,也减少了部分虚函数的查找开销。

到此这篇关于C++设计模式之CRTP的使用的文章就介绍到这了,更多相关C++设计模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言详解判断相同树案例分析

    C语言详解判断相同树案例分析

    这篇文章主要介绍了用C语言检查两棵树是否相同,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2022-04-04
  • C语言实现通讯录系统课程设计

    C语言实现通讯录系统课程设计

    这篇文章主要为大家详细介绍了C语言实现通讯录系统课程设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • Qt编写显示密码强度的控件

    Qt编写显示密码强度的控件

    这篇文章主要为大家详细介绍了Qt编写显示密码强度的控件,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • 如何查看进程实际的内存占用情况详解

    如何查看进程实际的内存占用情况详解

    本篇文章是对如何查看进程实际的内存占用情况进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 嵌入式C语言查表法在项目中的应用

    嵌入式C语言查表法在项目中的应用

    今天小编就为大家分享一篇关于嵌入式C语言查表法在项目中的应用,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • C语言中的函数指针学习笔记

    C语言中的函数指针学习笔记

    这篇文章主要介绍了C语言中的函数指针的一些学习知识点记录,文中作者整理了一些比较interesting的函数指针用法,需要的朋友可以参考下
    2016-04-04
  • C++控制台实现俄罗斯方块游戏

    C++控制台实现俄罗斯方块游戏

    这篇文章主要为大家详细介绍了C++控制台实现俄罗斯方块游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-06-06
  • 关于C++中的友元函数的一些总结

    关于C++中的友元函数的一些总结

    以下是对C++中的友元函数进行了详细的总结介绍,需要的朋友可以过来参考下
    2013-09-09
  • C++的静态类型检查详解

    C++的静态类型检查详解

    这篇文章主要为大家详细介绍了C++的静态类型检查,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • VSCode 配置C++开发环境的方法步骤

    VSCode 配置C++开发环境的方法步骤

    这篇文章主要介绍了VSCode 配置C++开发环境的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03

最新评论