C++ override private pure virtual method as public
为什么会这样?
http://coliru.stacked-crooked.com/a/e1376beff0c157a1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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();
} |
为什么我可以将私有虚拟方法重写为公共方法?
- 因为在A上调用它是可以的,但不是在基上?
- @ UKMonkey?什么?
- virtual和override只是不注意访问修饰符。值得注意的是,它不是函数签名的一部分,这正是virtual和override所看到的。
- @弗兰&231;瓦桑德里厄斯不是问题,为什么会这样?
- @弗兰&231;奥瓦安德里厄斯EMM…资料来源?
- 因为这就是私密的本质?子类不能从其父类访问(因此不能重写)私有方法。我建议你回去看看私人继承
- @但他们可以,这就是问题的关键。
- @120塔:Ukmonkey表示Base& base = a; base.do_run();会产生错误。
- 这是一个很好的问题。IMO这是语言设计中的一个错误,与"子类看不到私有成员"相矛盾。
- 不是一个复制品,但非常接近,并有解释性的答案:C++中的私有虚拟方法
- @Sebrockm举了一个很好的例子;假设run中的一些逻辑确保它不能运行两次。在我的具体实现中,不需要这种逻辑,因为它是在do_run中完成的(因为一些硬件特性,谁知道),这使它们完全相同;我可以安全地调用do_run或run;但我知道do_run会快一点,因为它不需要进行EDOCX1的检查。〔10〕。那么,为什么我不应该让那些知道他们正在使用这个实现的东西使用更快的版本;同时仍然允许那些不工作的东西继续工作?
- @Ukmonkey我不理解您的示例如何证明我作为基类作者允许子类作者重写我的方法是合理的,但不调用它,因为这是私有虚拟函数允许和禁止您这样做的。你能阐明你的观点吗?
- @Sebrockm--"私有成员不能被子类看到"是完全错误的。无法访问它们;它们的名称是可见的。
型
根据https://en.cppreference.com/w/cpp/language/virtual中的详细介绍,重写基的virtual成员函数只关心函数名、参数、const/volatile和ref限定符。它不关心返回类型、访问修饰符或其他您可能期望它关心的事情。
链接的引用还特别注意到:
Base::vf does not need to be visible (can be declared private, or inherited using private inheritance) to be overridden.
号
我找不到任何明确允许这样做的东西,但是覆盖规则并不能阻止它。它可以通过virtual函数和函数来覆盖现有的,而不是不允许这种情况发生。
如果你问为什么语言是这样的,你可能需要问标准化委员会。
- 好啊。。。那么,en.wikibooks.org/wiki/more_c%2b%2b_习语/非虚拟_interf‌&8203;ace应该如何工作呢?
- @我不明白你在问什么。该示例之所以有效,是因为可以重写private成员函数。
- 我的意思是,它的工作-但如果你能"意外地"覆盖它作为公共的,什么是nvi的意义?
- 如果您不小心覆盖了as public,您就误用了模式。我不确定您如何修改模式以减轻这种风险。但是C++中无数的特性和技术也是如此。使用它有可能出错。这是bug存在的主要原因之一。虽然在这种情况下,使它只会使您暴露在更多的问题中,但它本身不会引起任何问题。
- @Tower120当您声明该方法为虚方法时,尤其是纯虚方法时,必须实现它。如果你要的话,那不是偶然的
- @Killzonekid是的-但是作为公共的实现很容易被意外地完成。
- @Tower120您可以"意外"地将公共方法重写为私有方法,那又是什么呢?
- @我可以叫它Killzonekid。这将是接口冲突。我可以调用有意隐藏的函数。
- @如果你在另一个方向上犯了错误,并将其公之于众,你就不能公开打电话。谁来决定什么更糟?不过是个好问题
- @fran&231;oisandrieux"base::vf不需要可见(可以声明为私有,或者使用私有继承继承继承)即可被重写。"—但从另一方面来说,这并不意味着允许更改访问级别……
- @Tower120的要点是,派生类型的override被视为与基类型的virtual成员的匹配,而不考虑它们的访问修饰符。语言中的任何内容都不允许显式地执行此操作,但重写规则并不阻止执行此操作。这是允许的,因为它超越了现有的,而不是不允许这个案例。听起来您已经确信这是不允许的,并且正在寻找一个人来验证所有文档都是错误的。请注意,您没有更改任何访问修饰符。您提供的新功能具有不同的访问权限。
- @弗兰&231;奥瓦安德里厄斯不允许这样做,因为没有否认。请用这个更新答案。
- 但它确实关心返回类型。你可能想说的是,如果它们不相同,它们必须是协变的。
- @RAKETE1111是另一个问题。在确定成员函数是否重写或隐藏基类型的成员函数时,不考虑返回类型。一旦函数被确定为重写,返回类型就需要协变。例如,如果您更改我提到的四个元素中的任何一个,该函数将被视为隐藏基类型的函数。一般来说,这仍然会编译(除非使用override关键字)。如果更改返回类型,则会有一个格式错误的函数重写,编译器将对此进行投诉。
- @Fran&231;Oisandrieux ahh,这是因为函数不能仅使用返回类型重载,因此无法隐藏基函数,对吗?
- @我没有想到原因,但这是有道理的。
- @弗兰&231;你的阿凡达是一个奇怪的很好的匹配这个答案。嗯,鉴于你在C++标签中的活动,也许不仅仅是这个…
- "覆盖基地的虚拟"覆盖还是覆盖?
型
这种行为是有意的。如果一个方法是虚拟的,那么它就意味着可以由派生类自定义,而不考虑访问修饰符。
请看这里
型
请注意,此实现不会更改访问基类的方式和构造:
1 2
| Base& b = a;
b.do_run(); |
不起作用。
我记得Scott Meyers在"有效C++"中更详细地描述了它背后的一些原理。但是,关键的实际特性是能够在相反的方向上使用这种灵活性,在派生类中使用私有函数覆盖公共基类成员,从而强制客户机使用基类作为接口,而不希望直接使用应保持隐藏实现的派生类。
- 是的,覆盖、实现,但不更改可见性。
- @Tower120您可以更改派生类中成员的可见性,但是不管怎样,基类接口总是按照自己的定义工作。派生(实现)类中发生的任何事情都应该是一个实现细节,并且不太可见并且不影响基本接口。而且,通过提供实现,一些额外的灵活性有时是很方便的。
- @jszpilewski访问控制(public、protected、private)与可见性不同。
型
Why I can override PRIVATE virtual method as public???
号
因为您看到基方法在错误的角度上是私有的。B::do_run是私有的,意思是"只有这个阶级的成员和朋友才能使用它"。为了禁止派生类重写它,我们需要单独的说明符,但我们可以简单地使它不是virtual。另一方面,类A允许任何人打电话给A::do_run(),由类A设计师决定。所以你看不到隆起。
- 嗯,有一个提升:A既不是成员也不是朋友,所以我希望它无论如何都不能接触到B的私人功能。这是难以置信的前后矛盾。
- @FRAX A无法访问Base的私有功能。不能打电话给Base::do_run。它可以定制一个基础提供的定制点,但不涉及任何形式的访问。
- @cubbi通过与基类、不可访问的声明进行比较,检查重写基类声明的声明的有效性。说它不是一个"通道"真的是一种延伸。
- @好奇的家伙,你是在为"访问"这个词创造新的含义吗?它的技术含义(与"private:"相关)适用于编译函数调用。
- @库比我没有意识到一个不同于常识的技术意义。您的代码取决于声明。编译必须访问该声明。常识。这就是全部。
型
如果目的是为基类编写私有代码,以防止重写它的可能性,请在基类中实现私有函数,并声明它final,否则:什么时候应该使用私有虚拟机?isocpp.org常见问题解答
- 如果函数不应该被重写,就不要使其成为虚拟的。(我错过什么了吗?)
- 假设您有一个带有(没有虚拟的)void f1(int);的基类B和一个带有void f1(int);的派生类D(希望得到警告):d::f1()隐藏了b::f1()。VisualStudioC++编译器警告C26434代码分析。另一方面:如果您有带virtual void f1(int) final;的基类B和带void f1(int);的派生类D,则会得到一个错误:重写最终函数。
- 我明白了,但这努力值得吗?另外,将类的多态性作为一个(小)成本。
- 总的来说,隐藏名称是危险的,而且容易出错。在某些情况下,作为一个强有力的设计声明,我认为它值得"努力"。
- 但是编译器通常可以在不强制程序员使成员函数虚拟化的情况下警告隐藏名称。此外,该方法只能用于非静态成员函数,因此对于隐藏静态成员、数据成员、类型定义成员或非静态模板函数的情况,这不是一个修复方法。