目录
CRTP基本概念
CRTP 全称 Curiously Recurring Template Pattern
,即奇异递归模板模式。是一种 C++ 编程技巧,使用模板类和继承的组合来实现静态多态。该模式的关键思想是:在模板类的定义中,模板参数是当前类自身(通常是派生类)。这个技巧通常用于实现编译时多态,优化性能,C++中std:: enable_shared_from_this 也是一种CRTP的实践。。一般代码形式如下:
// 先定义一个模板类作为基类
template <typename T>
class Base
{
...
};
// 定义一个派生类,这个类继承以自身作为参数的基类
class Derived : public Base<Derived>
{
...
};
CRTP的基本原理
基类模板利用了其成员函数体(即成员函数的实现)在声明之后很久都不会被实例化(实际上只有被调用的模板类的成员函数才会被实例化),并利用了派生类的成员函数(通过类型转化)。
假设当前需要设计一个 Analimal类中包含 getCharacters() 接口,有不同的子类对该接口进行实现,很容易想到的方法是利用 C++ 的多态机制,实现大概是这样:
class Analimal
{
virtual std::string getCharacters() = 0;
};
class Cat: public Analimal
{
std::string getCharacters() override
{
return "Cat is a small animal...";
}
};
class Dog : public Analimal
{
std::string getCharacters() override
{
return "Dog is a loaty anmial...";
}
};
这样是传统动态多态的实现方法,在运行时通过查询虚函数表,找到实际调用接口,返回正确的类名。
CRTP 的形式如何实现:
template <typename T>
class Analimal
{
virtual std::string getCharacters()
{
// 强制转换为子类,调用子类的characters()
return static_cast<T *>(this)->characters();
}
};
class Cat: public Analimal<Cat>
{
std::string characters() override
{
return "Cat is a small animal...";
}
};
class Dog : public Analimal<Cat>
{
std::string characters() override
{
return "Dog is a loaty anmial...";
}
};
基类在编译时就可以知道派生类的信息!因此,以前是虚函数调用,现在是在编译期就将模板与正确的函数进行绑定。(通过继承实现虚函数的功能,又没有了虚函数调用产生的开销),这种“虚调用”不会产生过多的开销。编译器在编译时就一直跟踪调用方法,甚至会内联它。
CRTP与传统动态多态对比
特性 | CRTP(静态多态) | 动态多态 |
---|---|---|
性能 | 高效,无运行时开销(内联优化可能性大) | 有虚函数表查找开销,性能略低 |
灵活性 | 受限,类型在编译时固定 | 灵活,类型可以在运行时动态选择 |
类型安全性 | 高,编译时检查 | 低,存在类型转换失败风险 |
编译期 vs 运行期 | 完全在编译时 | 依赖运行时 |
耦合性 | 较高,A 模块使用 B 模块中的 CRTP 实现,涉及到的符号都得对 A 模块可见 | 较低,A 模块使用 B 模块中的接口类,接口实际实现的类不需要对 A 模块暴露 |
可读性 | 很差,涉及到模版,还存在代码体积膨胀问题 | 较差 |
使用场景
静态多态
以以上Analimal的代码为例,实际使用时。
template <typename T>
void getCharacterDsc(T& base){
base.getCharacters();
}
int main(){
Dog d;
Cat c;
getCharacterDsc(d);
getCharacterDsc(c);
return 0;
}
定义了一个函数getCharacterDsc()
,在其函数体内调用getCharacters()
函数。如果类型为Dog和Cat,则会调用这俩类型对应的characters()函数。即不使用virtual,也实现了多态功能,其二者的区别是:virtual是运行时多态,而CRTP则是在编译期就对模板进行了实例化,所以属于静态多态。
代码复用
现在需要实现一个功能,根据对象的具体类型,输出其类型名称。传统的动态多态写法如下,代码比较冗余。
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual void PrintType() const {
std::cout << typeid(*this).name() << std::endl;
}
};
class Derived : public Base {
public:
virtual void PrintType() const {
std::cout << typeid(*this).name() << std::endl;
}
};
class Derived1 : public Base {
public:
virtual void PrintType() const {
std::cout << typeid(*this).name() << std::endl;
}
};
void PrintType(const Base& base) {
base.PrintType();
}
使用CRTP则精简如下:
template<typename T>
class Base {
public:
void PrintType() {
T &t = static_cast<T&>(*this);
std::cout << typeid(t).name() << std::endl;
}
};
class Derived : public Base<Derived> {};
class Derived1 : public Base<Derived1> {};
template<typename T>
void PrintType(T base) {
base.PrintType();
}
项目优化中实践
在实际项目中单利模式具有极高的使用率,但是一个线程安全的单利模式书写需要及其仔细小心;以下是通过CRTP方式实现单利模板,在需要时仅仅需要继承即可,极大的提高了代码的复用率和可维护性。
统一的单利模板
#pragma once
#ifndef __SINGLETON_TEMPLATE_H__
#define __SINGLETON_TEMPLATE_H__
#include <QMutex>
#include <mutex>
template<typename T>
class Singleton
{
public:
static inline T* getInstance()
{
if (m_instance == 0)
{
std::lock_guard<std::mutex>(m_lock)
if (m_instance == 0)
{
m_instance = new T;
}
}
return m_instance;
}
static inline void free()
{
m_bFree = true;
std::lock_guard<std::mutex>(m_lock)
if (m_instance != 0)
{
delete m_instance;
m_instance = 0;
}
}
protected:
Singleton() {}
virtual ~Singleton()
{
if (!m_bFree)
{
std::lock_guard<std::mutex>(m_lock)
if (m_instance != 0)
{
delete m_instance;
m_instance = 0;
}
}
}
private:
Singleton(const Singleton&) = default;
private:
static T* m_instance;
static std::mutex m_lock;
static bool m_bFree;
};
template<typename T>
T* Singleton<T>::m_instance = NULL;
template<typename T>
std::mutex Singleton<T>::m_lock;
template<typename T>
bool Singleton<T>::m_bFree = false;
#endif // __SINGLETON_TEMPLATE_H__
单利模板的CRTP继承扩展
以下是以一个缓存类为例,继承单利模板,从而保证缓存类的全局唯一性。
#define g_DataCache Singleton<CacheHelper>::getInstance()
class CacheHelper:public<CacheHelper>
{
friend Singleton<CacheHelper>;
public:
string getValue(const string&key) const;
void setValue(const string&key, const string&value);
/*
...
*/
private:
CacheHelper() {}
~CacheHelper() {};
std::map<string, string>m_CacheMap;
/*
...
*/
};
单利类的使用示例
//使用
{
g_DataCache->setValue("name","xiaoming");
g_DataCache->setValue("sex", "men");
string name = g_DataCache->getValue("name");
string sex = g_DataCache->getValue("sex");
}
//软件退出
static void releaseResource()
{
//...
g_DataCache->free();
//...
}