C ++重载parsing
给出下面的例子,为什么我必须明确地使用语句b->A::DoSomething()
而不仅仅是b->DoSomething()
?
编译器的重载决议不应该找出我正在谈论的方法吗?
我正在使用Microsoft VS 2005.(注意:在这种情况下使用虚拟不起作用。)
class A { public: int DoSomething() {return 0;}; }; class B : public A { public: int DoSomething(int x) {return 1;}; }; int main() { B* b = new B(); b->A::DoSomething(); //Why this? //b->DoSomething(); //Why not this? (Gives compiler error.) delete b; return 0; }
两个“超载”不在同一个范围内。 默认情况下,编译器只考虑尽可能小的名称范围,直到find名称匹配。 之后完成参数匹配。 在你的情况下,这意味着编译器看到B::DoSomething
。 然后它试图匹配失败的参数列表。
一个解决办法就是把超载从A
降到B
的范围:
class B : public A { public: using A::DoSomething; // … }
重载parsing是C ++中最丑陋的部分之一
基本上,编译器在B的范围内find名称匹配的“DoSomething(int)”,看到参数不匹配,并停止并出现错误。
它可以通过使用B类中的A :: DoSomething来克服
class A { public: int DoSomething() {return 0;} }; class B : public A { public: using A::DoSomething; int DoSomething(int x) {return 1;} }; int main(int argc, char** argv) { B* b = new B(); // b->A::DoSomething(); // still works, but... b->DoSomething(); // works now too delete b; return 0; }
不,这种行为是为了确保你不会被错误地从远处的基类inheritance而来。
为了解决这个问题,你需要通过在B类中放置一个使用的A :: DoSomething来告诉编译器你想调用哪个方法。
请参阅这篇文章,以便快速简单地了解这种行为。
派生类中存在的方法隐藏了基类中所有具有相同名称(不pipe参数)的方法。 这样做是为了避免这样的问题:
class A {} ; class B :public A { void DoSomething(long) {...} } B b; b.DoSomething(1); // calls B::DoSomething((long)1));
后来有人改变了A类:
class A { void DoSomething(int ) {...} }
现在突然:
B b; b.DoSomething(1); // calls A::DoSomething(1);
换句话说,如果它不是这样工作的,那么一个你无法控制的类(A)中的一个无关的变化可能会默默地影响你的代码的工作方式。
这与名称parsing的工作方式有关。 基本上,我们首先find名称的范围,然后我们收集该范围内的所有重载名称。 然而,在你的情况下作用域是B类,而在B类,B :: DoSomething 隐藏 A :: DOSomething:
3.3.7名称隐藏[basic.scope.hiding]
… [剪断] …
3在成员函数定义中,本地名称的声明隐藏了具有相同名称的类的成员的声明; 请参阅basic.scope.class 。 派生类( class.derived )中的成员声明隐藏了同名基类成员的声明; 见class.member.lookup 。
由于名称隐藏,A :: DoSomething甚至不考虑重载parsing
当你在一个派生类中定义一个函数时,它将隐藏所有在基类中具有这个名字的函数。 如果基类函数是虚拟的并且具有兼容的签名,那么派生类函数也会覆盖基类函数。 但是,这并不影响可见性。
您可以使用using声明使基类函数可见:
class B : public A { public: int DoSomething(int x) {return 1;}; using A::DoSomething; };
这不是超载! 这是隐藏的!
当在inheritance树中search要使用的函数时,C ++使用没有参数的名称,一旦find它停止的定义,然后检查参数。 在给出的例子中,它在类B中停止。为了能够做到以后的事情,类B应该像这样定义:
class B : public A { public: using A::DoSomething; int DoSomething(int x) {return 1;}; };
该函数被隐藏在子类中具有相同名称的函数(但具有不同的签名)。 您可以使用using语句来取消隐藏,如使用A :: DoSomething();