我最近才知道,在 C++ 中,纯虚函数可以有一个主体。
这些功能的实际用例是什么?
经典的是纯虚析构函数:
class abstract {
public:
virtual ~abstract() = 0;
};
abstract::~abstract() {}
你让它纯粹是因为没有别的东西可以这样做,并且你希望这个类是抽象的,但是你仍然必须提供一个实现,因为派生类的析构函数显式地调用你的析构函数。是的,我知道,一个非常愚蠢的教科书示例,但因此它是经典之作。它一定出现在The C++ Programming Language的第一版中。
无论如何,我不记得曾经真正需要实现纯虚函数的能力。对我来说,这个功能存在的唯一原因似乎是它必须被明确禁止,而 Stroustrup 没有看到这样做的原因。
如果您觉得需要此功能,则您的设计可能走错了路。
有或没有主体的纯虚函数仅仅意味着派生类型必须提供它们自己的实现。
如果您的派生类想要调用您的基类实现,则基类中的纯虚函数体很有用。
抽象基类(具有纯虚函数)可能为其声明的纯虚函数提供实现的一个原因是让派生类有一个他们可以选择使用的简单“默认值”。与可以选择覆盖的普通虚函数相比,这并没有太多优势 - 事实上,唯一真正的区别是您强制派生类明确使用“默认”基类实现:
class foo {
public:
virtual int interface();
};
int foo::interface()
{
printf( "default foo::interface() called\n");
return 0;
};
class pure_foo {
public:
virtual int interface() = 0;
};
int pure_foo::interface()
{
printf( "default pure_foo::interface() called\n");
return 42;
}
//------------------------------------
class foobar : public foo {
// no need to override to get default behavior
};
class foobar2 : public pure_foo {
public:
// need to be explicit about the override, even to get default behavior
virtual int interface();
};
int foobar2::interface()
{
// foobar is lazy; it'll just use pure_foo's default
return pure_foo::interface();
}
我不确定是否有很多好处-也许在设计从抽象类开始的情况下,然后随着时间的推移发现许多派生的具体类都实现了相同的行为,因此他们决定移动该行为进入纯虚函数的基类实现。
我认为将通用行为放入纯虚函数的基类实现中也可能是合理的,派生类可能会被期望修改/增强/增强。
一种用例是从类的构造函数或析构函数调用纯虚函数。
C++ 标准委员会前主席、全能的 Herb Sutter确实给出了 3 个场景,您可以考虑提供纯虚拟方法的实现。
必须亲自说一下——我发现它们都没有说服力,并且通常认为这是 C++ 的语义缺陷之一。似乎 C++ 不遗余力地构建和拆分抽象父 vtable,而不是仅在子构建/销毁期间短暂暴露它们,然后社区专家一致建议不要使用它们。
带体的虚函数和带体的纯虚函数的唯一区别是第二个防止实例化的存在。您不能在 C++ 中标记类抽象。
在学习 OOD 和 C++ 时,这个问题确实令人困惑。就个人而言,我经常想到的一件事是: 如果我需要一个纯虚函数来实现,那么为什么首先要让它“纯”呢?为什么不只保留“虚拟”并拥有衍生产品,既受益又覆盖基本实现?
令人困惑的是,许多开发人员认为没有主体/实现是定义纯虚函数的主要目标/好处。这不是真的!
在大多数情况下,没有实体是具有纯虚函数的逻辑结果。拥有纯虚函数的主要好处是定义了一个合约:通过定义一个纯虚函数,您希望强制每个衍生品始终提供它们自己的函数实现。这个“契约方面”非常重要,特别是如果您正在开发类似公共 API 的东西。仅使函数虚拟化还不够,因为衍生产品不再被迫提供自己的实现,因此您可能会失去合约方面(在公共 API 的情况下可能会受到限制)。
俗话说: “虚函数可以被覆盖,纯虚函数必须被覆盖”。 而且在大多数情况下,合约是抽象的概念,因此对应的纯虚函数有任何实现是没有意义的。
但有时,因为生活很奇怪,您可能希望在衍生品之间建立强大的合约,并希望它们以某种方式从某些默认实现中受益,同时为合约指定自己的行为。即使大多数书籍作者建议避免让自己陷入这些情况,语言也需要提供安全网以防止最坏的情况发生!一个简单的虚函数是不够的,因为可能存在逃避合同的风险。所以 C++ 提供的解决方案是允许纯虚函数也能够提供默认实现。
上面引用的 Sutter 文章给出了具有纯虚函数和 body 的有趣用例。