【问题标题】:Private virtual method in C++C++中的私有虚方法
【发布时间】:2011-01-11 08:35:17
【问题描述】:

在 C++ 中将私有方法设为虚拟有什么好处?

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

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

【问题讨论】:

  • 我认为问题是倒退的。使某些东西成为虚拟的原因总是相同的:允许派生类覆盖它。那么问题应该是:将虚方法设为私有有什么好处?答案是:默认情况下将所有内容设为私有。 :-)
  • @ShreevatsaR 但你甚至没有回答你自己的问题......
  • @ShreevatsaR 我以为您的意思是以不同的方式倒退:使虚拟方法私有有什么好处?

标签: c++ polymorphism access-specifier


【解决方案1】:

Herb Sutter 已经很好地解释了它here

准则 #2:最好将虚拟函数设为私有。

这让派生类重写函数来自定义 根据需要进行行为,无需进一步暴露虚函数 直接通过使它们可由派生类调用(如 如果功能刚刚受到保护,则可能)。重点是 存在虚拟功能以允许定制;除非他们也需要 直接从派生类的代码中调用,没有 需要将它们设为私有

【讨论】:

  • 正如您可能从我的回答中猜到的那样,我认为 Sutter 的准则 #3 而不是将准则 #2 推到窗外。
  • 如果派生类需要重写方法但从那里调用父方法怎么办?这很常见,以至于我无法想象如果它阻止了私有虚拟设备会被推荐。 C++ 是否有类似super(...) 这样的机制来从覆盖的版本中调用父方法,即使它是私有的也可以工作?
  • @flarn2006 准则#3:只有当派生类需要调用虚函数的基实现时,才使虚函数受到保护。
【解决方案2】:

尽管所有调用都将虚拟成员声明为私有,但该论点根本站不住脚。通常,派生类对虚函数的覆盖将不得不调用基类版本。如果声明为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++ 委员会放宽“私有”以允许这种特定访问,我将全力支持私有虚拟函数。就目前而言,我们仍然被建议在马被偷后锁上谷仓的门。

【讨论】:

  • 我发现你的论点无效。作为 API 的开发人员,您应该努力寻找一个难以不正确使用的接口,而不是让其他开发人员为自己的错误做准备。您想要在示例中执行的操作可以仅使用私有虚拟方法来实现。
  • 这不是我要说的。但是你可以重构你的代码来达到同样的效果,而不需要调用私有基类函数
  • 在您的示例中,您希望扩展 set_data 的行为。指令m_data = ndata;cleanup(); 因此可以被认为是所有实现都必须保持的不变量。因此使cleanup() 成为非虚拟和私有的。添加对另一个虚拟私有方法和类的扩展点的调用。现在你的派生类不再需要调用基类的cleanup(),你的代码保持干净,你的接口很难被错误地使用。
  • @sigy 这只是移动球门柱。你需要超越最小的例子。当有更多的后代需要调用链中的所有 cleanup()s 时,参数就会分崩离析。或者您是否为链中的每个后代推荐一个额外的虚函数?伊克。甚至 Herb Sutter 也允许受保护的虚拟功能作为他的指导方针 3 中的一个漏洞。无论如何,如果没有一些实际的代码,你永远无法说服我。
  • 那我们同意不同意吧;)
【解决方案3】:

如果方法是虚拟的,它可以被派生类覆盖,即使它是私有的。当调用虚方法时,会调用被覆盖的版本。

(与 Prasoon Saurav 在回答中引用的 Herb Sutter 相反,C++ FAQ Lite recommends against private virtuals,主要是因为它经常使人们感到困惑。)

【讨论】:

  • C++ FAQ Lite 似乎已经改变了它的建议:“C++ FAQ 以前建议使用受保护的虚拟而不是私有虚拟。但是私有虚拟方法现在很常见,以至于混淆了新手不用担心。"
  • 然而,专家的困惑仍然是一个问题。坐在我旁边的四位 C++ 专业人士都不知道私有虚拟。
【解决方案4】:

我在阅读 Scott Meyers 的“Effective C++”时第一次遇到这个概念,第 35 条:考虑虚拟函数的替代方案。我想参考 Scott Mayers 以供其他可能感兴趣的人参考。 p>

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

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

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

  • 为什么要创建虚函数private?最好的原因是我们已经提供了一个public 面对方法。
  • 为什么不干脆把它改成protected,这样我就可以用这个方法来做其他有趣的事情了?我想这将始终取决于您的设计以及您认为基类适合的方式。我认为派生类制造商应该专注于实现所需的逻辑;其他一切都已经处理好了。此外,还有封装问题。

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

【讨论】:

    【解决方案5】:

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

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

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

    【讨论】:

    • 如果 t 可能有帮助,C++11 有 'final' 关键字以防止进一步覆盖。
    猜你喜欢
    • 2013-01-18
    • 1970-01-01
    • 1970-01-01
    • 2012-05-06
    • 2018-11-16
    • 2011-01-28
    • 2011-10-15
    • 2011-04-26
    • 2019-01-22
    相关资源
    最近更新 更多