【问题标题】:C++ override private pure virtual method as publicC ++将私有纯虚拟方法覆盖为公共
【发布时间】:2018-11-16 06:46:50
【问题描述】:

为什么会这样?

http://coliru.stacked-crooked.com/a/e1376beff0c157a1

class Base{
private:
    virtual void do_run() = 0;
public:
    void run(){
        do_run();
    }
};

class A : public Base {
public:
    // uplift ??
    virtual void do_run() override {}
};


int main()
{
    A a;
    a.do_run();
}

为什么我可以将 PRIVATE 虚拟方法重写为公共方法?

【问题讨论】:

  • virtualoverride 根本不关心访问修饰符。值得注意的是,它不是函数签名的一部分,这是 virtualoverride 看的。
  • @AlexG 但他们可以,这就是这个问题的重点。
  • @tower120: UKMonkey 表示Base& base = a; base.do_run(); 会产生错误。
  • 这是一个很好的问题。 IMO 这是语言设计中的错误,与“子类无法看到私有成员”相矛盾。
  • @sebrockm -- “子类看不到私有成员”是完全错误的。它们无法访问;他们的名字可见

标签: c++ oop virtual-functions private-members overriding


【解决方案1】:

根据https://en.cppreference.com/w/cpp/language/virtual#In_detail 覆盖基类的virtual 成员函数只关心函数名、参数、const/volatile-ness 和 ref 限定符。它不关心返回类型、访问修饰符或您可能期望它关心的其他事情。

链接的参考还特别指出:

Base::vf 不需要可见(可以声明为私有,或使用私有继承继承)即可被覆盖。

我能找到的任何东西都没有明确允许这样做,但覆盖规则并没有阻止它。凭借virtual 函数和覆盖现有的函数并且不允许这种情况,它是允许的。

如果您要问为什么语言是这样的,您可能需要询问标准化委员会。

【讨论】:

  • @tower120 我不明白你在问什么。该示例有效因为 private 成员函数可以被覆盖。
  • 如果你不小心覆盖了public,你就误用了这个模式。我不确定如何修改模式以降低风险。但同样适用于 c++ 中无数的特性和技术。使用它可能会出错。这是存在错误的主要原因之一。虽然在这种情况下,将其设为 public 只会让您面临更多问题,但它本身不会导致任何问题。
  • @tower120 关键是派生类型的override 被视为基类型的virtual 成员的匹配项,而不管它们的访问修饰符如何。语言中没有任何内容明确允许这样做,但覆盖规则并没有阻止它。它是通过覆盖现有的而不是不允许这种情况来允许的。听起来您已经确信这是不允许的,并且正在寻找某人来验证所有文档是否错误。请注意,您没有更改任何访问修饰符。您正在提供具有不同访问权限的新功能。
  • 确实关心返回类型。您可能想说的是,如果它们不相同,则它们必须是协变的。
  • @FrançoisAndrieux 你的头像非常适合这个答案。好吧,鉴于您在 C++ 标记中的活动,也许不仅仅是这个……
【解决方案2】:

这种行为是有意的。如果一个方法是虚拟的,那么它意味着可以由派生类自定义,而不管访问修饰符是什么。

here

【讨论】:

  • 这有点打破了私有继承的私有性。
【解决方案3】:

为什么我可以将 PRIVATE 虚拟方法重写为 public???

因为您以错误的角度看待私有的基本方法。 B::do_run 是私有的意味着“只有这个班级的成员和朋友可以使用它”。为了禁止派生类覆盖它,我们需要单独的说明符,但我们可以简单地使其不是virtual。另一边的A 类允许任何人调用A::do_run(),这由A 类的设计师决定。所以没有你看到的隆起。

【讨论】:

  • 嗯,有一个提升:A 既不是会员也不是朋友,所以我希望它无法访问B 的私人功能。这令人难以置信的不一致。
  • @Frax A 无权访问 Base 的私有函数。它无法呼叫Base::do_run。它可以自定义 Base 提供的自定义点,但这不涉及任何形式的访问。
  • @Cubbi 通过与基类不可访问声明进行比较来检查覆盖基类声明的声明的有效性。说它不是“访问”确实有点牵强。
  • @curiousguy 您是否正在为“访问”这个词创造新的含义?它的技术含义(与“私有:”相关)适用于编译函数调用。
  • @Cubbi 我不知道与常识含义不同的技术含义。您的代码取决于声明。编译必须访问该声明。常识。就是这样。
【解决方案4】:

请注意,此实现不会改变访问基类和构造的方式:

Base& b = a;
b.do_run();

不会工作。

我记得 Scott Meyers 在“Effective C++”中更详细地描述了它背后的一些基本原理。但关键的实用特性是能够在相反的方向使用这种灵活性,在派生类中用私有函数覆盖公共基类成员,强制客户端使用基类作为接口,而不是直接使用派生的应该是一个隐藏的实现。

【讨论】:

  • 是的,覆盖、实施,但不改变可见性。
  • @tower120 您可以更改派生类中成员的可见性,但无论如何,基类接口将始终按照自己的定义工作。派生(实现)类中发生的任何事情都应该是实现细节,并且不那么明显并且不会影响基接口。通过提供实现,一些额外的灵活性有时可能会很方便。
  • @jszpilewski 访问控制(公共、受保护、私有)与可见性相同。
【解决方案5】:

如果打算为基类编写私有代码并防止覆盖它的可能性,请在基类中实现私有函数,并声明它final,否则:什么时候应该有人使用私有虚拟? ISOCPP.ORG FAQ

【讨论】:

  • 如果函数不应该被覆盖,就不要让它成为虚拟的。 (我错过了什么吗?)
  • 假设您有一个带有(无虚拟)void f1(int); 的基类 B 和一个带有 void f1(int); 的派生类 D - 不好(希望收到警告):D::f1()隐藏 B::f1()。 Visual Studio C++ 编译器警告C26434 代码分析。另一方面:如果你有带有virtual void f1(int) final; 的基类B 和带有void f1(int); 的派生类D - 你会得到一个错误:覆盖最终函数。
  • 我明白了,但值得付出努力吗?此外,将类多态化为(小)成本。
  • 总体而言,名称隐藏是危险且容易出错的。在某些情况下,作为一个强有力的设计声明,我认为它值得“努力”。
  • 但是编译器通常可以警告名称隐藏,而无需强制程序员将成员函数设为 virtual final。此外,该方法只能用于非静态成员函数,因此对于隐藏静态成员、数据成员、类型定义成员或非静态模板函数的情况无法解决。
猜你喜欢
  • 1970-01-01
  • 2014-08-21
  • 2011-01-28
  • 2011-08-10
  • 2010-10-03
  • 2011-11-02
  • 2017-06-08
相关资源
最近更新 更多