C++奇技淫巧:CRTP模式详解——用编译时多态替代虚函数

什么是CRTP?

CRTP(Curiously Recurring Template P

目录

什么是CRTP?

基本结构 

为什么需要CRTP?

CRTP的四大核心作用

1. 静态多态(编译时多态)

2. 代码复用

3. 接口约束

4. 性能优化

经典应用场景

1. 运算符重载(Boost.Operators)

2. 策略模式(编译时策略)

3. 单例模式

实例

实战注意事项

✅ 正确用法

⚠️ 常见陷阱

性能对比测试

总结


attern,奇异递归模板模式)是C++模板元编程中的经典技巧。其核心思想是:基类模板以派生类作为模板参数,派生类继承自该基类模板。通过这种递归的模板参数传递,可以在编译期实现静态多态,避免运行时虚函数开销。

基本结构 

template <typename Derived>
class Base {
    // 基类通过Derived访问派生类成员
};

class MyClass : public Base<MyClass> {  // 关键:派生类将自己作为模板参数
    // 实现基类依赖的接口
};

为什么需要CRTP?

传统面向对象通过虚函数实现运行时多态,但存在以下问题:

  • 性能开销:虚函数通过虚表(vtable)查询,影响性能

  • 二进制膨胀:虚表增加内存占用

  • 灵活性限制:运行时多态难以进行编译时优化

CRTP通过编译时多态解决这些问题,尤其适用于高性能计算、嵌入式开发等场景。

CRTP的四大核心作用

1. 静态多态(编译时多态)

基类通过static_cast<Derived*>(this)直接访问派生类方法,无需虚函数:

template <typename Derived>
class Animal {
public:
    void Speak() {
        static_cast<Derived*>(this)->SpeakImpl(); // 编译时绑定
    }
};

class Cat : public Animal<Cat> {
public:
    void SpeakImpl() { std::cout << "Meow!\n"; }
};

class Dog : public Animal<Dog> {
public:
    void SpeakImpl() { std::cout << "Woof!\n"; }
};

// 使用
Cat().Speak(); // 输出Meow!
Dog().Speak(); // 输出Woof!

2. 代码复用

基类可为派生类提供通用实现。例如实现计数器功能:

template <typename T>
class Counter {
protected:
    Counter() = default;
public:
    size_t GetCount() const { return count_; }
    
    T& operator++() {
        ++count_;
        return *static_cast<T*>(this);
    }
private:
    size_t count_ = 0;
};

class MyObject : public Counter<MyObject> {};
// 自动获得计数功能
MyObject obj;
++obj;

3. 接口约束

强制派生类实现特定方法(编译期检查):

template <typename T>
class Serializable {
public:
    void Save() {
        static_cast<T*>(this)->Serialize();
    }
};

// 若未实现Serialize,编译报错!
class Data : public Serializable<Data> {
public:
    void Serialize() { /* 必须实现 */ }
};

4. 性能优化

消除虚函数调用开销,对比测试:

// 动态多态(虚函数)
virtual void foo() { ... }  // 每次调用需查虚表

// CRTP静态多态
void foo() { static_cast<T*>(this)->fooImpl(); }  // 直接内联调用

经典应用场景

1. 运算符重载(Boost.Operators)

// 自动生成operator!=(基于operator==)
template <typename T>
class EqualityComparable {
public:
    friend bool operator!=(const T& a, const T& b) {
        return !(a == b); // 要求派生类实现operator==
    }
};

class Point : public EqualityComparable<Point> {
public:
    bool operator==(const Point& other) const { 
        return x == other.x && y == other.y;
    }
private:
    int x, y;
};

2. 策略模式(编译时策略)

template <typename Impl>
class LogPolicy {
public:
    void Write(const std::string& msg) {
        static_cast<Impl*>(this)->WriteImpl(msg);
    }
};

class FileLogger : public LogPolicy<FileLogger> {
    void WriteImpl(const std::string& msg) { /* 写入文件 */ }
};

class ConsoleLogger : public LogPolicy<ConsoleLogger> {
    void WriteImpl(const std::string& msg) { /* 输出到控制台 */ }
};

3. 单例模式

template <typename T>
class Singleton {
public:
    static T& GetInstance() {
        static T instance;
        return instance;
    }
protected:
    Singleton() = default;
};

class AppConfig : public Singleton<AppConfig> { 
    // 自动获得单例能力
};

实例

单例模板

#ifndef SINGLETON_H
#define SINGLETON_H
#include <memory>
#include <mutex>
#include <iostream>
#include "global.h"

using namespace std;
template <typename T>
class Singleton {
protected:
    Singleton() = default;
    Singleton(const Singleton<T>&) = delete;
    Singleton& operator=(const Singleton<T>& st) = delete;

    static std::shared_ptr<T> _instance;
public:
    static std::shared_ptr<T> GetInstance() {
        static std::once_flag s_flag;
        std::call_once(s_flag, [&]() {
            _instance = shared_ptr<T>(new T);
            });

        return _instance;
    }
    void PrintAddress() {
        std::cout << _instance.get() << endl;
    }
    ~Singleton() {
        std::cout << "this is singleton destruct" << std::endl;
    }
};

template <typename T>
std::shared_ptr<T> Singleton<T>::_instance = nullptr;
#endif // SINGLETON_H

Httpmgr类继承以自己为模板参数的模板单例类

#ifndef HTTPMGR_H
#define HTTPMGR_H
#include "singleton.h"
#include <QString>
#include <QUrl>
#include <QObject>
#include <QNetworkAccessManager>
#include "global.h"


class HttpMgr:public QObject, public Singleton<HttpMgr>,
        public std::enable_shared_from_this<HttpMgr>
{
    Q_OBJECT

public:
    ~HttpMgr();
    void PostHttpReq(QUrl url, QJsonObject json, ReqId req_id, Modules mod);
private:
    friend class Singleton<HttpMgr>;
    HttpMgr();
    QNetworkAccessManager _manager;
public slots:
    void slot_http_finish(ReqId id, QString res, ErrorCodes err, Modules mod);
signals:
    void sig_http_finish(ReqId id, QString res, ErrorCodes err, Modules mod);
    //注册模块http相关请求完成发送此信号
    void sig_reg_mod_finish(ReqId id, QString res, ErrorCodes err);
    void sig_reset_mod_finish(ReqId id, QString res, ErrorCodes err);
    void sig_login_mod_finish(ReqId id, QString res, ErrorCodes err);
};

#endif // HTTPMGR_H

那么HttpMgr类也成了单例类了

实战注意事项

✅ 正确用法

适用场景:高频调用的基础组件(如数学库、网络库)、需要编译时多态的策略模式、运算符重载等。


希望这篇博客能帮助您深入理解CRTP!如果觉得有用,欢迎点赞⭐️收藏!有疑问或补充,欢迎留言讨论~

  • 确保派生类正确传递自身类型

    class Good : public Base<Good> {};  // ✅
    class Bad : public Base<Other> {};  // ❌ 灾难!

    ⚠️ 常见陷阱

  • 对象切片(Object Slicing)
    1.基类不应有非静态成员变量

    // 错误示例!
    Base<Derived> obj = Derived();  // 派生类成员被切割

    2.无限递归模板实例化
    避免循环依赖:

  • class A : public Base<B> {}; 
    class B : public Base<A> {}; // ❌ 编译爆炸!

    性能对比测试

    通过Benchmark测试(单位:纳秒/操作):

    操作虚函数CRTP
    简单函数调用3.20.8
    循环100万次调用3200800

    总结

    CRTP的三大优势

  • 零运行时开销:编译期绑定

  • 更强的类型约束:接口检查提前到编译时

  • 代码高度复用:通用逻辑下沉到基类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值