在Effective C++中,Scott Meyers给出了一个示例,当您通过继承重用代码时,它很有用.他从这开始:
struct Airplane {
virtual void fly() {
// fly the plane
}
...
};
struct ModelA : Airplane { ... };
struct ModelB : Airplane { ... };
Run Code Online (Sandbox Code Playgroud)
现在,ModelA和ModelB以相同的方式飞行,并且这被认为是飞行飞机的常用方式,因此代码在基类中.然而,并非所有飞机都以这种方式飞行,我们打算将飞机变为多态,因此它是虚拟的.
现在我们添加必须以不同方式飞行的ModelC,但我们犯了一个错误:
struct ModelC : Airplane { ... (no fly function) };
Run Code Online (Sandbox Code Playgroud)
哎呀.ModelC将崩溃.迈耶斯希望编译器警告我们我们的错误.
因此,他fly使用实现在飞机中制作纯虚拟,然后在ModelA和ModelB中放置:
void fly() { Airplane::fly(); }
Run Code Online (Sandbox Code Playgroud)
现在,除非我们显式地在我们想要的默认行为飞我们的派生类中的状态,我们不明白这一点.因此,编译器也告诉我们,而不仅仅是文档告诉我们所有需要检查我们的新平面模型的东西.
这样做,但我觉得它有点弱.理想情况下,我们有一个BoringlyFlyable mixin包含fly的默认实现,并以这种方式重用代码,而不是将代码放在一个基类中,该基类承担飞机不是飞机要求的某些事情.但是如果fly函数实际上做了任何重要的事情,那就需要CRTP :
#include <iostream>
struct Wings {
void flap() { std::cout << "flapping\n"; }
};
struct Airplane {
Wings wings;
virtual void fly() = 0;
};
template <typename T>
struct BoringlyFlyable {
void fly() {
// planes fly by flapping their wings, right? Same as birds?
// (This code may need tweaking after consulting the domain expert)
static_cast<T*>(this)->wings.flap();
}
};
struct PlaneA : Airplane, BoringlyFlyable<PlaneA> {
void fly() { BoringlyFlyable<PlaneA>::fly(); }
};
int main() {
PlaneA p;
p.fly();
}
Run Code Online (Sandbox Code Playgroud)
当PlaneA声明从BoringlyFlyable继承时,它通过接口断言它以默认方式传播它是有效的.请注意,BoringlyFlyable可以定义自己的纯虚函数:也许getWings是一个很好的抽象.但由于它是一个模板,它没有必要.
我有一种感觉,这种模式可以替代所有你提供纯虚函数和实现的情况 - 实现可以改为mixin,如果他们想要它可以继承类.但是我无法立即证明这一点(例如,如果Airplane::fly使用私有成员那么它需要相当多的重新设计才能这样做),并且可以说CRTP对于初学者来说有点高性能.它还有更多的代码实际上没有增加功能或类型安全性,它只是明确了Meyer设计中隐含的内容,有些东西可以通过拍打它们的翅膀来飞行,而其他东西则需要做其他的东西.所以我的版本绝不是一个完全的嘘声.
在GotW#31中得到了解决.摘要:
您可能会有三个主要原因.#1很常见,#2非常罕见,而#3是与弱编译器一起工作的高级程序员偶尔使用的解决方法.
大多数程序员应该只使用#1.
...这适用于纯虚拟析构函数.
| 归档时间: |
|
| 查看次数: |
303 次 |
| 最近记录: |