动态多态
动态多态的优点是可以实现多质变量的容器,然后遍历执行同名函数,产生不同效果。 缺点显然就是vptr的寻址开销
namespace VirtualFunction {
struct IShape {
virtual ~IShape() = default;
virtual float Area() = 0;
};
struct Circle : public IShape {
float r = 1;
float Area() override {
return 3.14f * r * r;
}
};
struct Rect : public IShape {
float a = 1, b = 1;
float Area() override {
return a * b;
}
};
void f() {
IShape *circle = new Circle();
IShape *rect = new Rect();
std::cout << circle->Area() << rect->Area();
delete circle;
delete rect;
}
}
静态多态
模版函数
用模版函数,把抽象类作为模版参数传入,然后执行,虽然表达上能体现多态性, 但是单纯这样似乎没有存在的意义。毕竟在使用多态时,不会一个个手动的去创建对象执行函数。
因此优点是编译器多态,没有运行时额外开销;缺点是做不到“多质变量容器遍历”【除非通过variant,后文会讲】
namespace NormalTemplate {
struct Circle {
float r = 1;
float Area() const { return 3.14f * r * r; }
};
struct Rect {
float a = 1, b = 1;
float Area() const { return a * b; }
};
template<typename Shape>
float Area(const Shape &shape) {
return shape.Area();
}
void f(){
Circle circle;
Rect rect;
std::cout << Area(circle) << Area(rect);
}
}
CRTP
普通的模版函数的实现能表达多态语义,但是存在没有更多的合理性; 而CRTP除了能表达多态语义(is-a),在本质上它是一种has-ability的语义.
is-a就是熟知的继承关系,Circle继承自Shape,自然有Shape计算Area的功能; 而多个CRTP Derived类表达的是这些类都有一些共同的能力,这个能力不是从Base那边继承过来的(虽然好像听起来和多态语义也差不多)。
在应用中,CRTP存在的意义就是简化代码,而不是异质容器遍历。
比如我有很多卡牌,都希望它有复制自身属性的能力
struct Card{}
struct CardA : public Card{
CardA Clone(){
return CardA(*this);
}
int a;
}
struct CardB : public Card{
CardB Clone(){
return CardB(*this);
}
float b;
}
如果这样写,每多一个卡牌都要重新写一下Clone函数,此时就可以通过CRTP,表达卡牌拥有复制自身到能力,来实现多个类有相似函数的简化写法
template<typename Derived>
struct Card {
Derived Clone() {
return Derived(*static_cast<Derived *>(this));
};
};
struct CardA : public Card<CardA> {
int a = 1;
};
struct CardB : public Card<CardA> {
float b = 1;
};
void f(){
CardA a;
auto a1 = a.Clone();
}
这种方式非常常见,各种开源项目中都有出现,比如UE的TCommands
再来一个体现CRTP多态语义的例子
namespace CRTP {
template<typename Derived>
struct IShape {
float Area() {
return dynamic_cast<Derived *>(this)->Area();
}
};
struct Circle : public IShape<Circle> {
float r = 1;
float Area() {
return 3.14f * r * r;
}
};
struct Rect : public IShape<Rect> {
float a = 1, b = 1;
float Area() {
return a * b;
}
};
void f() {
Circle circle;
Rect rect;
std::cout << circle.Area() << rect.Area();
}
}
同样的,这样的多态也是提供不了异质容器遍历
用Variant改造静态多态
通过Variant,就可以强行赋予两种静态多态异质容器遍历, 虽然好像没见哪个开源软件这样写过。
namespace NormalVariant {
struct Circle {
float r = 1;
float Area() const {
return 3.14f * r * r;
}
};
struct Rect {
float a = 1, b = 1;
float Area() const {
return a * b;
}
};
template<typename Shape>
float Area(const Shape &shape) {
return shape.Area();
}
void f() {
std::variant<Circle, Rect> shape;
std::vector<std::variant<Circle, Rect>> ShapeList;
for (const auto &item: ShapeList) {
std::visit([](auto &&arg) {
std::cout << Area(arg);
}, item);
}
}
}
思考一下这有什么缺点导致没什么人用(猜测)
- variant保存data指针和额外的一个int Index指示当前存储的类型, 内存开销更大
- visit写法让人分解,仿函数也不便于debug