我知道纯虚函数可以实现。但是,为什么会这样呢?这两个概念是否存在冲突?有什么用?任何人都可以提供任何例子吗?
答案 0 :(得分:7)
在 Effective C ++ 中,Scott Meyers给出了一个示例,当您通过继承重用代码时,它很有用。他从这开始:
struct Airplane {
virtual void fly() {
// fly the plane
}
...
};
struct ModelA : Airplane { ... };
struct ModelB : Airplane { ... };
现在,ModelA和ModelB以相同的方式飞行,并且这被认为是飞行飞机的常用方式,因此代码在基类中。但是,并非所有平面都以这种方式飞行,我们希望平面具有多态性,因此它是虚拟的。
现在我们添加了必须以不同方式飞行的ModelC,但我们犯了一个错误:
struct ModelC : Airplane { ... (no fly function) };
糟糕。 ModelC将崩溃。迈耶斯希望编译器警告我们我们的错误。
因此,他使用实现在飞机中使fly
纯虚拟,然后在ModelA和ModelB中,放置:
void fly() { Airplane::fly(); }
现在除非我们在派生类中明确地表明我们想要默认的飞行行为,否则我们不会得到它。因此编译器告诉我们的不仅仅是文档告诉我们所有需要检查我们的新平面模型的东西。
这可以胜任,但我觉得它有点弱。理想情况下,我们有一个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();
}
当PlaneA声明从BoringlyFlyable继承时,它通过接口断言它以默认方式传播它是有效的。请注意,BoringlyFlyable可以定义自己的纯虚函数:也许getWings
将是一个很好的抽象。但由于它是一个模板,它不需要。
我觉得这个模式可以替换所有你提供纯虚函数和实现的情况 - 实现可以改为mixin,如果他们想要它们可以继承类。但我无法立即证明(例如,如果Airplane::fly
使用私有成员,则需要进行相当多的重新设计才能这样做),并且可以说CRTP对初学者来说有点高性能。它还有更多的代码实际上没有增加功能或类型安全性,它只是明确了Meyer的设计中隐含的东西,有些东西可以通过拍打它们的翅膀飞行而其他人需要做其他的东西。所以我的版本绝不是一个完全的嘘声。
答案 1 :(得分:6)
在GotW #31中解决了。总结:
您可能有三个主要原因 做这个。 #1很平常,#2是 非常罕见,#3是一种解决方法 偶尔使用先进的 程序员与弱者合作 编译器。
大多数程序员应该只使用#1。
...适用于纯虚拟析构函数。
答案 2 :(得分:2)
这两个概念没有冲突,尽管它们很少一起使用(因为OO纯粹主义者无法协调它,但这超出了这个问题/答案的范围)。
这个想法是给纯虚拟函数一个实现,同时强制子类覆盖该实现。子类可以调用基类函数来提供一些默认行为。基础无法实例化(它是“抽象”),因为虚拟函数是纯,即使它可能有实现。
答案 3 :(得分:1)
Wikipedia总结得很好:
虽然是纯虚方法 通常没有实现 声明它们的类,纯粹的 允许使用C ++中的虚方法 包含他们的实现 宣布上课,提供后备或 派生类的默认行为 如果合适,可以委托。
通常,您不需要为纯虚拟机提供基类实现。但有一个例外:pure virtual destructors。实际上,如果您的基类具有纯虚拟析构函数it must have an implementation。为什么需要纯虚拟析构函数而不仅仅是虚拟析构函数?通常,为了使基类抽象而不需要执行任何其他方法。例如,在您可能合理地使用任何方法的默认实现的类中,但您仍然不希望人们实例化基类,您只能将析构函数标记为纯虚拟。
编辑:
以下是一些代码,用于说明调用基本实现的几种方法:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void DoIt() = 0;
};
class Der : public Base
{
public:
void DoIt();
};
void Base::DoIt()
{
cout << "Base" << endl;
}
void Der::DoIt()
{
cout << "Der" << endl;
Base::DoIt();
}
int main()
{
Der d;
Base* b = &d;
d.DoIt();
b->DoIt(); // note that Der::DoIt is still called
b->Base::DoIt();
return 0;
}
答案 4 :(得分:0)
通过这种方式,您可以提供有效的实现,但仍然需要子类实现者明确地调用该实现。
答案 5 :(得分:0)
嗯,我们已经有了一些很好的答案......我在写作时要慢一点......
我的想法是例如一个尝试{} catch {}的init函数,这意味着它不应该放在构造函数中:
class A {
public:
virtual bool init() = 0 {
... // initiate stuff that couldn't be made in constructor
}
};
class B : public A{
public:
bool init(){
...
A::init();
}
};