动态多态和静态多态
动态多态的优点是可以实现多质变量的容器,然后遍历执行同名函数,产生不同效果。 缺点显然就是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除了能表达多态语义(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改造静态多态
Section titled “用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