目录
- 1.奇异递归模板CRTP
-
- [1.1 介绍](#1.1 介绍)
- [1.2 使用场景](#1.2 使用场景)
- [1.3 代码复用](#1.3 代码复用)
-
- [1.3.1 访问者模式](#1.3.1 访问者模式)
- [1.3.2 奇异递归模板实现访问者模式](#1.3.2 奇异递归模板实现访问者模式)
- [1.4 编译时多态](#1.4 编译时多态)
- [1.5 优势总结](#1.5 优势总结)
1.奇异递归模板CRTP
1.1 介绍
CRTP(Curiously Recurring Template Pattern)是一种常用的设计模式,通过将派生类作为模板参数传递给基类,允许基类使用派生类的特性。以下是 CRTP 的基本实现:
cpp
template<typename T>
struct Base {
T value;
Base(T v) : value(v) {}
void print() {
std::cout << value << std::endl;
}
};
template<typename T>
struct Derived : public Base<T> {
using Base<T>::Base;
void print() {
std::cout << "Derived: " << this->value << std::endl;
}
};
在这个示例中,Base 类接受一个类型 T,并且 Derived 类通过 CRTP 继承了 Base,可以重写 print 方法,展示了如何实现静态多态。
1.2 使用场景
- 代码复用:由于子类派生基类,可以复用基类的方法
- 编译时多态:基类是一个模板类,能够获得传递进来的派生类,然后可以调用派生类的方法,达到多态的效果,与运行时多态相比没有虚表开销。
1.3 代码复用
1.3.1 访问者模式
访问者模式是一种对象行为模式,它允许在不修改类的情况下向类添加新的操作。以下是访问者模式的实现:
cpp
class TextFile;
class VideoFile;
struct Visitor {
virtual void visit(VideoFile&) = 0;
virtual void visit(TextFile&) = 0;
virtual ~Visitor() = default;
};
struct Elem {
virtual void accept(Visitor& v) = 0;
virtual ~Elem() = default;
};
struct VideoFile : Elem {
virtual void accept(Visitor& v) override {
v.visit(*this);
}
};
struct TextFile : Elem {
virtual void accept(Visitor& v) override {
v.visit(*this);
}
};
struct ConcreteVisitor : Visitor {
void visit(VideoFile& video) override {
std::cout << "Visiting Video File" << std::endl;
}
void visit(TextFile& text) override {
std::cout << "Visiting Text File" << std::endl;
}
};
通过 Visitor 接口,我们可以轻松地为不同的文件类型实现访问逻辑,而无需更改文件类的代码。
1.3.2 奇异递归模板实现访问者模式
使用奇异递归模板(CRTP)实现代码复用是一个有效的方法,可以减少重复代码并增强类型安全性。以下是一个改进后的示例,展示如何通过 CRTP 来实现 AutoDispatchedElem 类,结合访问者模式进行代码复用。
cpp
#include <iostream>
// 前向声明
class VideoFile2;
class TextFile2;
// 访问者接口
struct Visitor2 {
virtual void visit(VideoFile2&) = 0;
virtual void visit(TextFile2&) = 0;
virtual ~Visitor2() = default;
};
// 基类
struct Elem2 {
virtual void accept(Visitor2& v) = 0;
virtual ~Elem2() = default;
};
// CRTP 基类
template<typename T>
struct AutoDispatchedElem : public Elem2 {
void accept(Visitor2& v) override {
v.visit(static_cast<T&>(*this));
}
};
// VideoFile2 类
struct VideoFile2 : AutoDispatchedElem<VideoFile2> {
void print() {
std::cout << "Video File 2" << std::endl;
}
};
// TextFile2 类
struct TextFile2 : AutoDispatchedElem<TextFile2> {
void print() {
std::cout << "Text File 2" << std::endl;
}
};
// 具体的 Visitor 实现
struct ConcreteVisitor2 : Visitor2 {
void visit(VideoFile2& video) override {
video.print();
}
void visit(TextFile2& text) override {
text.print();
}
};
// 测试函数
int test2() {
VideoFile2 video;
TextFile2 text;
ConcreteVisitor2 visitor;
// 访问 VideoFile2 和 TextFile2
video.accept(visitor);
text.accept(visitor);
return 0;
}
int main() {
test2();
return 0;
}
代码解析:
CRTP 基类 AutoDispatchedElem:
该类继承自 Elem2,并实现了 accept 方法。通过使用 static_cast,它将 this 转换为特定的类型 T,允许访问者调用特定的 visit 方法。
VideoFile2 和 TextFile2 类:
这两个类分别继承自 AutoDispatchedElem,在其中实现 print 方法。它们的 accept 方法会被自动派发到合适的访问者实现。
ConcreteVisitor2 类:
这个类实现了 Visitor2 接口,具体实现了 visit 方法,用于打印 VideoFile2 和 TextFile2 的信息。
通过 CRTP,AutoDispatchedElem 类能够复用代码,同时保持类型安全。访问者模式和 CRTP 的结合使得代码更为简洁,易于维护。你可以根据需要扩展此模式,添加更多文件类型或操作。
1.4 编译时多态
通过 CRTP,我们可以实现编译时多态,避免虚函数调用的开销。
cpp
#include <iostream>
// CRTP 基类
template<typename T>
struct Animal {
// 调用具体实现的方法
void bark() {
static_cast<T&>(*this).barkImpl();
}
};
// Cat 类
class Cat : public Animal<Cat> {
public:
void barkImpl() { std::cout << "Meow" << std::endl; }
};
// Dog 类
class Dog : public Animal<Dog> {
public:
void barkImpl() { std::cout << "Woof" << std::endl; }
};
// 统一的操作函数
template<typename T>
void play(Animal<T>& animal) {
animal.bark();
}
int main() {
Cat cat;
Dog dog;
// 通过统一接口调用各自的行为
play(cat); // 输出: Meow
play(dog); // 输出: Woof
return 0;
}
代码解析
CRTP 基类 Animal:
Animal 模板类接受一个类型参数 T,并定义了一个公共方法 bark。在这个方法中,使用 static_cast 将 this 转换为 T 类型,然后调用 barkImpl 方法。这个方法在派生类中实现。
派生类 Cat 和 Dog:
Cat 和 Dog 类分别继承自 Animal 和 Animal。它们实现了具体的 barkImpl 方法,分别输出 "Meow" 和 "Woof"。
操作函数 play:
play 函数接受一个 Animal 的引用,调用 bark 方法。这种方式允许我们以统一的接口操作不同类型的动物。
1.5 优势总结
- 性能优化:由于没有虚函数的开销,程序执行速度更快。
- 编译时类型检查:类型错误在编译时被捕获,提高了代码的安全性。
- 易于扩展:只需创建新的派生类并实现 barkImpl 方法,就可以轻松扩展新类型。
通过 CRTP,我们可以在 C++ 中实现高效的编译时多态。此模式使得代码更加简洁、可维护,同时也提高了运行效率。使用 CRTP 的模式在许多实际应用中都非常有用,尤其是在需要高性能的场景中。