C + + 中的私有虚拟方法

在 C + + 中使私有方法虚拟化有什么好处?

我在一个开源的 C + + 项目中注意到了这一点:

class HTMLDocument : public Document, public CachedResourceClient {
private:
virtual bool childAllowed(Node*);
virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};
88732 次浏览

Herb Sutter 已经很好地解释了 给你

准则 # 2: 更喜欢将虚函数设置为私有。

这允许派生类重写函数以自定义 行为,而不需要进一步公开虚函数 直接通过使它们可由派生类调用来实现 如果函数只是受到保护,那么这些函数是可能的) 存在允许自定义的虚拟函数; 除非它们还需要 从派生类的代码中直接调用,则不存在 不能让他们有私人空间

如果该方法是虚方法,那么它可以被派生类重写,即使它是私有的。当调用虚方法时,将调用重写版本。

(与 Prasoon Saurav 在他的回答中引用的 Herb Sutter 相反,C + + FAQ Lite 建议反对私人虚拟,主要是因为它经常让人感到困惑。)

我使用它们来允许派生类为基类“填充空白”,而不向最终用户暴露这样的漏洞。例如,我有一个从公共基础派生的高状态对象,它只能实现整个状态机的2/3(派生类根据模板参数提供剩余的1/3,而基础因为其他原因不能成为模板)。

为了使许多公共 API 能够正确工作,我需要使用通用的基类(我使用的是可变模板) ,但是我不能让这个对象失控。更糟糕的是,如果我把弹坑留在状态机中——以纯虚函数的形式——任何地方,而不是“私有”,我就会允许一个聪明或无知的用户从其子类派生出来,以覆盖用户永远不应该触摸的方法。因此,我将状态机“大脑”放入 PRIVATE 虚函数中。然后,基类的直接子类填充其非虚拟重写上的空格,用户就可以安全地使用结果对象或创建自己的进一步派生类,而不必担心弄乱状态机。

关于不应该有公共虚方法的论点,我认为是扯淡。用户可以不正确地覆盖私有虚拟,就像覆盖公共虚拟一样容易——毕竟他们正在定义新的类。如果公共不应该修改给定的 API,那么不要让它在公共可访问对象中成为虚拟的。

我第一次接触这个概念是在读 Scott Meyers 的《有效的 C + + 》 项目35: 考虑虚拟函数的替代方案。的时候,我想引用 Scott Mayers 的文章给其他可能感兴趣的人。

它是 通过非虚拟界面模板方法的一部分: 公共面向方法不是虚拟的; 相反,它们包装了私有的虚拟方法调用。然后,基类可以在私有虚函数调用之前和之后运行逻辑:

public:
void NonVirtualCalc(...)
{
// Setup
PrivateVirtualCalcCall(...);
// Clean up
}

我认为这是一个非常有趣的设计模式,我相信您可以看到添加的控件是多么有用。

  • 为什么要使虚函数 private? 最好的理由是我们已经提供了一个面向 public的方法。
  • 为什么不简单地将它设置为 protected,这样我就可以将该方法用于其他有趣的事情?我想这将永远取决于您的设计以及您认为基类适合的方式。我认为派生类制造者应该专注于实现所需的逻辑; 其他的一切都已经处理好了。还有封装的问题。

从 C + + 的角度来看,重写私有虚方法是完全合法的,即使您不能从类中调用它。这支持上述设计。

尽管所有的调用都声明一个虚拟成员是私有的,但是这个参数根本站不住脚。通常,派生类对虚函数的重写必须调用基类版本。如果声明为 private就不行:

class Base
{
private:


int m_data;


virtual void cleanup() { /*do something*/ }


protected:
Base(int idata): m_data (idata) {}


public:


int data() const { return m_data; }
void set_data (int ndata) { m_data = ndata; cleanup(); }
};


class Derived: public Base
{
private:
void cleanup() override
{
// do other stuff
Base::cleanup(); // nope, can't do it
}
public:
Derived (int idata): base(idata) {}
};

您可以使用 来声明基类方法 protected

然后,您必须采取一种难看的权宜之计,即通过注释指示应该重写但不调用该方法。

class Base
{
...
protected:
// chained virtual function!
// call in your derived version but nowhere else.
// Use set_data instead
virtual void cleanup() { /* do something */ }
...

因此 Herb Sutter 的指导方针 # 3... 但是不管怎样,马已经跑出了马厩。

当您声明某些 protected内容时,您隐式地信任任何派生类的写入程序能够理解并正确地使用受保护的内部结构,就像 friend声明意味着对 private成员有更深的信任一样。

如果用户因为违反了这种信任而产生不良行为(例如,因为懒得阅读你的文档而被贴上“笨手笨脚”的标签) ,那么他们只能怪自己。

更新 : 我收到了一些反馈,声称您可以使用私有虚函数以这种方式“链接”虚函数实现。如果是的话,我很想看看。

我使用的 C + + 编译器肯定不会让派生类实现调用私有基类实现。

如果 C + + 委员会放宽“私有”以允许这种特定的访问,我将完全支持私有虚拟函数。就目前情况来看,我们仍然被建议在马被偷之后锁上谷仓的门。