C++中的私有虚方法

sil*_*rgh 118 c++ polymorphism access-specifier

在C++中创建私有方法虚拟的优点是什么?

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

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};
Run Code Online (Sandbox Code Playgroud)

Pra*_*rav 112

Herb Sutter 在这里非常好地解释了它.

准则#2:更喜欢将虚拟功能设为私有.

这使得派生类可以根据需要覆盖函数来自定义行为,而无需通过派生类调用它们来直接暴露虚函数(如果函数只是受到保护,则可能).关键是存在虚拟功能以允许定制; 除非它们还需要直接从派生类的代码中调用,否则不需要将它们变成私有的

  • 如果派生类需要重写该方法但从内部调用父方法怎么办?这种情况很常见,我无法想象如果私有虚拟机阻止了这种情况,我们会推荐它。C++ 是否有像“super(...)”这样的机制来从重写版本中调用父方法,即使它是私有的也可以工作? (2认同)

sth*_*sth 63

如果方法是虚拟的,它可以被派生类覆盖,即使它是私有的.调用虚方法时,将调用重写的版本.

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

  • 看来C++ FAQ Lite已经改变了它的建议:"_C++ FAQ以前建议使用受保护的虚拟而不是私有虚拟.但是私有虚拟方法现在很普遍,新手的混淆不那么令人担忧." (36认同)
  • 然而,专家的困惑仍然是一个问题.坐在我旁边的四位C++专业人员都不知道私人虚拟. (11认同)

Spe*_*cer 10

尽管所有要求将虚拟成员声明为私有的呼吁,但这个论点根本就没有用水.通常,派生类重写虚函数必须调用基类版本.如果声明它就不能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) {}
};
Run Code Online (Sandbox Code Playgroud)

必须声明基类方法protected.

然后,你必须采取丑陋的权宜之计,通过注释表明该方法应该被覆盖但不被调用.

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

因此Herb Sutter的指导方针#3 ......但无论如何,这匹马已经离开了谷仓.

当您声明某些内容时,protected您隐含地信任任何派生类的编写者以理解并正确使用受保护的内部,只是friend声明意味着对private成员的更深信任.

因违反该信任而遭受不良行为的用户(例如,通过不打扰阅读您的文档而标记为"无能为力")只能归咎于自己.

更新:我收到了一些反馈,声称您可以使用私有虚拟功能以这种方式"链接"虚拟功能实现.如果是这样,我肯定希望看到它.

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

如果C++委员会放宽了"私有"以允许这种特定访问,那么我将全部用于私有虚拟功能.目前,我们仍被建议在马被盗后锁上谷仓门.

  • 我不是那个意思。但是您可以重构代码以达到相同的效果,而无需调用私有基类函数 (3认同)
  • 在您的示例中,您想扩展set_data的行为。因此,可以将指令“ m_data = ndata;”和“ cleanup();”视为对于所有实现都必须成立的不变式。因此,使`cleanup()`为非虚拟私有。将调用添加到另一个私有的虚拟方法和类的扩展点。现在,您的派生类不再需要调用base的`cleanup()`,您的代码将保持干净,并且您的接口很难被错误地使用。 (3认同)
  • 你的两个论点都没有通过我的检验。应该从类中提取清理行为,留下具有多态清理策略的具体类。组合应该优先于继承。即使我们超越了最小的例子,任何其他行为也同样适用。一个类应该努力承担单一责任。指南 - 仅覆盖纯虚函数。 (3认同)
  • 我发现你的论点无效.作为API的开发人员,您应该努力寻找一个难以正确使用的界面,而不是为了您自己的错误而设置另一个开发人员.您在示例中想要执行的操作只能使用私有虚拟方法实现. (2认同)
  • @sigy IE 具有一系列非虚拟保护的“实现”函数和糟糕的包装函数。这些扭曲只会混淆你的代码。非虚函数同样可能被滥用。 (2认同)
  • @sigy 那只是移动了球门柱。您需要超越最小的例子。当有更多后代需要调用链中的所有 `cleanup()` 时,该参数就会失效。或者您是否为链中的每个后代推荐一个额外的虚函数?哎呀。甚至 Herb Sutter 也允许受保护的虚拟函数作为他的指南 #3 中的漏洞。无论如何,如果没有一些实际的代码,您将永远无法说服我。 (2认同)
  • 然后让我们同意不同意;) (2认同)

Poo*_*ven 9

我在阅读Scott Meyers的"Effective C++"时第一次遇到这个概念,第35项:考虑虚拟功能的替代方案.我想引用Scott Mayers给其他可能感兴趣的人.

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

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }
Run Code Online (Sandbox Code Playgroud)

我认为这是一个非常有趣的设计模式,我相信你可以看到增加的控件是如何有用的.

  • 为什么要制作虚拟功能private?最好的理由是我们已经提供了一种public面向方法.
  • 为什么不简单地做到这protected一点,以便我可以将该方法用于其他有趣的事情?我想它总是取决于你的设计以及你如何相信基类适合.我认为派生类制造者应该专注于实现所需的逻辑; 其他一切都已经得到了解决.此外,还有封装问题.

从C++的角度来看,覆盖私有虚拟方法是完全合法的,即使您无法从类中调用它.这支持上述设计.


小智 5

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

我需要拥有公共基类才能使许多公共 API 正常工作(我正在使用可变参数模板),但我不能让该对象随意使用。更糟糕的是,如果我将状态机中的坑以纯虚拟函数的形式留在“Private”之外的任何地方,我就会允许从其子类之一派生的聪明或无知的用户覆盖用户永远不应该接触的方法。因此,我将状态机“大脑”放在私有虚拟函数中。然后,基类的直接子类填充其非虚拟覆盖上的空白,用户可以安全地使用生成的对象或创建自己的进一步派生类,而不必担心弄乱状态机。

至于你不应该拥有公共虚拟方法的论点,我说的是废话。用户可以像公共虚函数一样轻松地不正确地覆盖私有虚函数——毕竟他们正在定义新的类。如果公众不应该修改给定的 API,那么就不要在可公开访问的对象中将其设为虚拟。