当前位置: 首页 > article >正文

一文讲清 C++ CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)

CRTP是 C++ 中的一种模板元编程技术,其核心原理是通过模板继承静态多态,在编译期实现基类对派生类成员的访问,从而避免运行时虚函数调用的开销。

1. CRTP 的基本结构

CRTP 的核心思想是:基类是一个模板类,其模板参数是派生类本身

典型的代码如下:

template <typename Derived>
class Base {
public:
    void interface() {
        // 基类通过静态转换调用派生类的实现
        static_cast<Derived*>(this)->implementation();
    }
};

class Derived : public Base<Derived> { // 派生类将自己作为模板参数传递给基类
public:
    void implementation() {
        // 派生类的具体实现
    }
};

2. CRTP 的核心原理

2.1 模板继承

       2.1.1基类模板化:基类 Base 接受派生类 Derived 作为模板参数

       2.1.2派生类继承基类模板:派生类继承时,将自己作为模板参数传递给基类,形成递归依赖(Derived : public Base<Derived>

2.2 静态多态

       2.2.1 编译期绑定:基类通过 static_cast<Derived*>(this) 将 this 指针转换为派生类类型,直接调用派生类的方法(如 implementation())。

       2.2.2 无虚函数开销:无需虚函数表(vtable),函数调用在编译期确定,无运行时间接跳转。

       2.2.3 代码复用与扩展:基类可定义通用逻辑(如 interface()),而具体实现由派生类提供(如 implementation())。新增派生类时,只需实现特定方法,无需修改基类。

3 CRTP 的工作流程

       3.1模板实例化:当定义 Derived 类时,基类 Base<Derived> 被实例化,生成针对 Derived 的基类代码

       3.2方法调用:调用 Base<Derived>::interface() 时,通过 static_cast 将基类指针安全转换为派生类指针,直接调用 Derived::implementation()

  3.3编译期解析:所有类型转换和函数绑定在编译期完成,生成高效代码

4 CRTP示例

#include <iostream>
using namespace std;


// 定义一个模板基类,接受派生类作为参数
template<typename T>
class Animal {
public:
    void sound() {
        // static_cast到派生类类型,调用派生类的soundImpl()
        cout << "Animal::sound" <<endl;
        static_cast<T*>(this)->soundImpl();
    }
};

// 具体的狗类,继承自Animal<Dog>
class Dog : public Animal<Dog> {   
public:
    // 具体实现狗的叫声
    void soundImpl() {
        cout << "Woof!" << endl;
    }
};

// 具体的猫类,继承自Animal<Cat>
class Cat : public Animal<Cat> {    
public:
    void soundImpl() {
        cout << "Meow!" << endl;
    }
};

int main() {
    // 创建一个Dog对象,调用sound()
    Animal<Dog> dog;
    dog.sound(); // 输出 Woof!

    // 创建一个Cat对象,调用sound()
    Animal<Cat> cat;
    cat.sound(); // 输出 Meow!

    return 0;
}

//运行结果:
//Animal::sound
//Woof!
//Animal::sound
//Meow!

4.1汇编分析:

.LC0:
        .string "Woof!"
Dog::soundImpl():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char>>& std::operator<<<std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*)
        mov     esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&)
        mov     rdi, rax
        call    std::ostream::operator<<(std::ostream& (*)(std::ostream&))
        nop
        leave
        ret
.LC1:
        .string "Meow!"
Cat::soundImpl():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     esi, OFFSET FLAT:.LC1
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char>>& std::operator<<<std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*)
        mov     esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&)
        mov     rdi, rax
        call    std::ostream::operator<<(std::ostream& (*)(std::ostream&))
        nop
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        lea     rax, [rbp-1]
        mov     rdi, rax
        call    Animal<Dog>::sound() 
        lea     rax, [rbp-2]
        mov     rdi, rax
        call    Animal<Cat>::sound()
        mov     eax, 0
        leave
        ret
.LC2:
        .string "Animal::sound"
Animal<Dog>::sound():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     esi, OFFSET FLAT:.LC2
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char>>& std::operator<<<std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*)
        mov     esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&)
        mov     rdi, rax
        call    std::ostream::operator<<(std::ostream& (*)(std::ostream&))
        mov     rax, QWORD PTR [rbp-8]
        mov     rdi, rax
        call    Dog::soundImpl()
        nop
        leave
        ret
Animal<Cat>::sound():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     esi, OFFSET FLAT:.LC2
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char>>& std::operator<<<std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*)
        mov     esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&)
        mov     rdi, rax
        call    std::ostream::operator<<(std::ostream& (*)(std::ostream&))
        mov     rax, QWORD PTR [rbp-8]
        mov     rdi, rax
        call    Cat::soundImpl()
        nop
        leave
        ret
		

重点关注一下main函数中对sound的调用

lea rax, [rbp-1]            ; 对象地址
mov rdi, rax
call Dog::sound()           ; 直接调用

5 同样的功能,用动态多态实现

class Animal
{
public:
    virtual void sound()
    {
        cout << "Animal::sound" <<endl;
    }
};

class Dog : public Animal
{
    virtual void sound()
    {
        cout << "Woof!"  << endl;
    }
};

class Cat: public Animal
{
    virtual void sound()
    {
        cout << "Meow!"  << endl;
    }
};

int main() {
    Animal* animal1 = new Dog();
    animal1->sound();

    Animal* animal2 = new Cat();
    animal2->sound();


    return 0;
}

//程序运行结果:
//Woof!
//Meow!

对应的汇编:

.LC0:
        .string "Animal::sound"
Animal::sound():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char>>& std::operator<<<std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*)
        mov     esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&)
        mov     rdi, rax
        call    std::ostream::operator<<(std::ostream& (*)(std::ostream&))
        nop
        leave
        ret
.LC1:
        .string "Woof!"
Dog::sound():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     esi, OFFSET FLAT:.LC1
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char>>& std::operator<<<std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*)
        mov     esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&)
        mov     rdi, rax
        call    std::ostream::operator<<(std::ostream& (*)(std::ostream&))
        nop
        leave
        ret
.LC2:
        .string "Meow!"
Cat::sound():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     esi, OFFSET FLAT:.LC2
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char>>& std::operator<<<std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*)
        mov     esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&)
        mov     rdi, rax
        call    std::ostream::operator<<(std::ostream& (*)(std::ostream&))
        nop
        leave
        ret
Animal::Animal() [base object constructor]:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     edx, OFFSET FLAT:vtable for Animal+16
        mov     rax, QWORD PTR [rbp-8]
        mov     QWORD PTR [rax], rdx
        nop
        pop     rbp
        ret
Dog::Dog() [base object constructor]:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        mov     rdi, rax
        call    Animal::Animal() [base object constructor]
        mov     edx, OFFSET FLAT:vtable for Dog+16
        mov     rax, QWORD PTR [rbp-8]
        mov     QWORD PTR [rax], rdx
        nop
        leave
        ret
Cat::Cat() [base object constructor]:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        mov     rdi, rax
        call    Animal::Animal() [base object constructor]
        mov     edx, OFFSET FLAT:vtable for Cat+16
        mov     rax, QWORD PTR [rbp-8]
        mov     QWORD PTR [rax], rdx
        nop
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 24
        mov     edi, 8
        call    operator new(unsigned long)
        mov     rbx, rax
        mov     QWORD PTR [rbx], 0
        mov     rdi, rbx
        call    Dog::Dog() [complete object constructor]
        mov     QWORD PTR [rbp-24], rbx
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        mov     rdx, QWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    rdx
        mov     edi, 8
        call    operator new(unsigned long)
        mov     rbx, rax
        mov     QWORD PTR [rbx], 0
        mov     rdi, rbx
        call    Cat::Cat() [complete object constructor]
        mov     QWORD PTR [rbp-32], rbx
        mov     rax, QWORD PTR [rbp-32]
        mov     rax, QWORD PTR [rax]
        mov     rdx, QWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-32]
        mov     rdi, rax
        call    rdx
        mov     eax, 0
        mov     rbx, QWORD PTR [rbp-8]
        leave
        ret
vtable for Cat:
        .quad   0
        .quad   typeinfo for Cat
        .quad   Cat::sound()
vtable for Dog:
        .quad   0
        .quad   typeinfo for Dog
        .quad   Dog::sound()
vtable for Animal:
        .quad   0
        .quad   typeinfo for Animal
        .quad   Animal::sound()
typeinfo for Cat:
        .quad   vtable for __cxxabiv1::__si_class_type_info+16
        .quad   typeinfo name for Cat
        .quad   typeinfo for Animal
typeinfo name for Cat:
        .string "3Cat"
typeinfo for Dog:
        .quad   vtable for __cxxabiv1::__si_class_type_info+16
        .quad   typeinfo name for Dog
        .quad   typeinfo for Animal
typeinfo name for Dog:
        .string "3Dog"
typeinfo for Animal:
        .quad   vtable for __cxxabiv1::__class_type_info+16
        .quad   typeinfo name for Animal
typeinfo name for Animal:
        .string "6Animal"		

重点关注一下main函数中sound的调用过程

push    rbp             ; 保存旧的基址指针(rbp)
mov     rbp, rsp        ; 将当前栈指针(rsp)设为新的基址指针(rbp)
push    rbx             ; 保存rbx寄存器(被调用者保存寄存器)
sub     rsp, 24         ; 分配24字节栈空间(用于局部变量和对齐)

mov     edi, 8          ; 参数:申请8字节内存(Dog对象的大小)
call    operator new(unsigned long)  ; 调用new运算符分配内存
mov     rbx, rax        ; 将返回的指针保存到rbx(rax存放new的返回值)
mov     QWORD PTR [rbx], 0  ; 将内存的前8字节置零(临时初始化虚表指针vptr)
mov     rdi, rbx        ; 将对象指针作为构造函数参数(this指针)
call    Dog::Dog() [complete object constructor]  ; 调用Dog构造函数
mov     QWORD PTR [rbp-24], rbx  ; 将构造后的Dog指针存入栈[rbp-24]

//对应C++代码:
//Animal* dog = new Dog(); // 动态分配Dog对象

//调用Dog对象的虚函数

mov     rax, QWORD PTR [rbp-24]  ; 从栈中加载Dog对象指针到rax
mov     rax, QWORD PTR [rax]     ; 加载虚表指针vptr(Dog的虚表地址)
mov     rdx, QWORD PTR [rax]     ; 加载虚表第一个条目(即Dog::sound()地址)
mov     rax, QWORD PTR [rbp-24]  ; 再次加载Dog对象指针到rax
mov     rdi, rax                 ; 将对象指针作为this参数传递
call    rdx                      ; 通过虚表调用Dog::sound()

//对应C++代码:
//dog->sound(); // 动态绑定到Dog::sound()

6 通过对比,关键差异总结如下:

特性动态多态(虚函数)静态多态(CRTP)
实现方式虚函数表 + 运行时间接调用模板继承 + 编译期绑定
内存开销每个对象需存储 vptr(通常8字节)无额外内存开销
性能函数调用需查表跳转(轻微性能损耗)直接调用(无额外开销)
灵活性支持运行时多态仅支持编译期多态
代码扩展性新增派生类无需修改基类需修改模板参数或继承关系
汇编特征虚表结构、vptr初始化、间接调用(call rdx直接函数调用(call Dog::sound()


http://www.kler.cn/a/595436.html

相关文章:

  • 基于Nvidia Jetson Nano边缘计算设备使用TensorRT部署YOLOv8模型实现目标检测推理
  • CHM(ConcurrentHashMap)中的 sizeCtl 的作用与值变化详解
  • 批量删除 PPT 空白幻灯片页面
  • 车载以太网网络测试-20【传输层-DOIP协议-3】
  • C语言复习笔记--数组
  • IPD的分析工具与模型(十一)利用Ansoff矩阵制定产品市场组合,帮助企业确定增
  • 通过《电幻国度》来看机器人的发展
  • OpenCV旋转估计(2)用于自动检测波浪校正类型的函数autoDetectWaveCorrectKind()
  • B2-DPO:开启去中心化物联网(DePIN)的智能革命
  • Python之使用mitmproxy进行本地化网络监听
  • KNN算法
  • python中的元组、字典与集合
  • CUL-CHMLFRP启动器 windows图形化客户端
  • HR人员和组织信息同步AD域服务器实战方法JAVA
  • 基于Neo4j的文物推荐系统
  • MySQL 客户端连不上(1045 错误)原因全解析
  • 爱普生SG2016CAN晶振优势和多领域应用
  • 在Fedora-Workstation-Live-x86_64-41-1.4中使用最新版本firefox和腾讯翻译插件让英文网页显示中文翻译
  • 【数据结构】C语言实现线索二叉树
  • 树莓集团南京产业园:战略定位背后的深度思考