关于C++ Primer中“透过基类调用被屏蔽的虚函数”

关于C++ Primer中“通过基类调用被屏蔽的虚函数”
在C++ Primer 15.5.4中有一个例子,看了很久没有看懂,请教一下大家(分割线中是书中的原话);
===============================================================
class Base { 
     public: 
         virtual int fcn(); 
     }; 
     class D1 : public Base { 
     public: 
          // hides fcn in the base; this fcn is not virtual 
          int fcn(int); // parameter list differs from fcn in Base 
          // D1 inherits definition of Base::fcn() 
     }; 
     class D2 : public D1 { 
     public: 
         int fcn(int); // nonvirtual function hides D1::fcn(int) 
         int fcn();    // redefines virtual fcn from Base 
     }; 
从 Base 继承的虚函数不能通过 D1 对象(或 D1 的引用或指针)调用,因为该函数被 fcn(int) 的定义屏蔽了。

通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类:

     Base bobj;  D1 d1obj;  D2 d2obj;
     Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
     bp1->fcn();   // ok: virtual call, will call Base::fcnat run time
     bp2->fcn();   // ok: virtual call, will call Base::fcnat run time
     bp3->fcn();   // ok: virtual call, will call D2::fcnat run time
===============================================================

对于bp1和bp3,都没有问题,但是对于bp2 ,为什么是可以调用的,存在如下疑问:

1) bp2 实际指向的是D1 类型的对象,但是D1 类 中定义了fcn,屏蔽了Base中的fcn,为什么会调用Base中的fcn而不出错?
2) 对于“通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类” 这句话,无法理解;对于虚函数,不是应该动态绑定吗?动态绑定的后果就是很有可能调用派生类中重新定义的虚函数;为什么这里讲“忽略派生类”? 如果忽略了派生类,如何实现动态绑定?
------解决方案--------------------
覆盖(也叫做多态)是指派生类重新实现或者改写了基类的成员函数,其特征是:1、不同的作用域(分别位于派生类和基类中)。2、函数名称相同搜索。3、函数的参数也完全相同。4、基类必须有virtual关键字。
注意第三点,class D1 中的int fcn(int) 并不满足多态的条件,也无法动态绑定,因此只能调用基类的函数
------解决方案--------------------
单看那一句,是错的。
但要联系上下文。
“通过基类类型的引用或指针调用(虚)函数时,(如果子类中没有重写该虚函数且定义了一个同名函数),编译器将在基类中查找该(虚)函数而忽略派生类(的同名函数)”
------解决方案--------------------
撸主啊   这个估计没人能帮助你了   悟性。
不过也不急,你还年轻,我也是反反复复纠结了很多遍之后才懂了。
同样是看这本书,effective C++ ,你不要乱黑我们的  meyers 哦,遮掩也要在子类的作用域。
简单的  你需要理解  作用域、静态类型、动态类型。

复杂的,深入的,彻底的,请看:《深度探索C++对象模型》。
------解决方案--------------------
引用:
Quote: 引用:

Quote: 引用:

Quote: 引用:

1)  D2中存在两个fcn,分别是基类继承来的,和自己定义的。因为参数列表不同,这是重载的两个不同函数。
->我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数


2)看上下文,肯定有具体情况,一句摘出来的话不能判断书里具体是要表达什么意思。

--> 书中就孤零零的这一句

首先,在例子的前面一页有一个深色强调句子:如果派生类定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。也就是D1 d1; d1.fcn(); 这样的调用是违法的。

然后,给的例子是为了说明“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法。

最后,“关键概念:名字查找与继承”说明什么这样的调用是合法的。前两条简单,直接略过。第三条,在基类找到了满足bp2->fcn(); 的原型,调用合法。第四条,普通函数的话,就调用基类的成员函数。虚函数则通过虚函数表查找。“如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行那个函数版本”。我的理解是通过动态调用到D1中的 fcn() 但是此时 fcn() 作用域仍然在基类中(因为是通过基类指针调用的),没有被屏蔽,调用合法。

之前我也没有注意到这个细节,有什么错误欢迎后面的高手指正。


如果把“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法”当成一个C++的定律的话,确实可以解释bp2->fcn() 可以成功的原因;
那么,关于effective C++中的遮掩,可能需要加一个限制条件,就是遮掩只发生在使用派生类的对象来调用成员函数的情况下,而不适合于用基类指针或引用(虽然指向派生类的对象)来调用成员函数的情况; 


查了下,虚函数调用编译时被处理成 pObj->_vptr->vtable[]。也就是对编译器来说,bp2->fcn() 调用的仍然是Base::fcn()而不是D1::fcn();所以屏蔽作用在这个时候是不存在的。程序运行期间会根据bp2绑定的实际类去vtable[]里面查实际应该调用的函数。
------解决方案--------------------
引用:
Quote: 引用:

前面的说错了,应该是这样
1>D1中的fcn也是虚函数,你不声明编译器帮你完成,而且是继承来的。D1中也有两个fcn函数,一个是继承来的,一个是自己定义的,也就是函数的重载,你在调用的时候没有传参,所以自动调用的是基类的fcn。
2>"通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类"这句话是没有问题的,动态绑定我不懂,我就说我对这句话的理解吧。  这句话的意思是当你在基类中定义了一个虚函数的时候,通过基类的指针或者引用调用该函数的时候会直接调用基类的这个函数,而忽略了子类。原因呢是因为所有的子类都会继承基类的虚函数,而如果在子函数中定义了同名同参的函数的时候,基类的虚函数会自动隐藏,也就是说此时使用基类类型的指针调用该函数的时候调用的就是子类中的函数。


关于1,我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数
关于2,我认为你说的和这句话矛盾,这句话说忽略派生类,但是你说的却会“调用子类(派生类)中的函数”,如果忽略了派生类,还如何调用派生类函数呢?

关于1: 事实上  你可以用代码试一下 而不是一味的讨论  毕竟实践才是真理