我有以下代码:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void WhoAmI() const;
typedef void (Base::*WhoPtr)() const;
};
class Derived : public Base
{
public:
virtual void WhoAmI() const;
};
void Derived::WhoAmI() const
{
cout << "I am the derived" << endl;
}
void Base::WhoAmI() const
{
cout << "I am the base" << endl;
}
int main()
{
Base::WhoPtr func = &Base::WhoAmI;
Base theBase;
(theBase.*func)();
Derived theDerived;
(theDerived.*func)();
cin.get();
return 0;
}
让我们专注于主要:
int main()
{
Base::WhoPtr func = &Base::WhoAmI;
Base theBase;
(theBase.*func)();
Derived theDerived;
(theDerived.*func)();
cin.get();
return 0;
}
我们有一个本地变量func
,其中包含Base::WhoAmI
的地址。
此外,我们还有Base
和Derived
个对象。
在第2行,我们从基地调用指向func
:(theBase.*func)()
。
直到现在我才明白。
之后的2行,我们从派生的(theDerived.*func)()
中调用它。
打印:I am the derived
。为什么呢?
这两个WhoAmI
都是virtual
,这意味着呼叫依赖于pointed object
,而不是类型。
指向的对象是func
,属于Base
。为什么打印I am the derived
而不是I am the base
?
答案 0 :(得分:1)
你为什么感到惊讶你有一个指向成员函数的指针
指向虚拟功能。如果你拿了地址
theDerived
或对其的引用,并初始化Base*
或
一个Base&
,你希望ptrToBase->WhoAmI()
来调用它
派生类中的函数。毕竟,这就是你使用的原因
一个虚拟的功能开始。当你这么做时
通过指向成员函数的指针调用。表达方式
&Base::WhoAmI
产生一个指向(虚拟)成员函数的指针。
答案 1 :(得分:0)
指向的对象是theDerived
。您选择的方法是Base::whoAmI
,请注意方法名称包含类引用(静态)但不包含对象引用(动态)。要调用的虚函数由对象的运行类型时间决定,该对象用作方法的this
。
答案 2 :(得分:0)
关于虚函数的重点是,它是一个运行时决定,根据相关对象的动态类型调用哪个版本。这与非虚函数调用非常不同,其中编译器本身根据对象的声明的类型做出决策,而不管实际运行时对象的类型是什么。
为了实现这一点,每个类都有一个虚函数表(一个vtable),它的所有实例都有一个运行时的隐式指针。现在,当您创建Base实例时,实例的vtable指针将指向Base的vtable。同样,Derived的实例将指向Derived的vtable。
在这两个vtable中,您的示例中WhoAmI()
只有一个条目,Base的vtable中的指针指向Base::WhoAmI()
,而Derived的vtable点中的指针指向Derived::WhoAmI()
到WhoAmI()
。
因此,当您调用WhoAmI()
时,运行时将从对象中查找vtable,然后查找将要执行的函数的函数指针。
这就是说,从你看到的行为中可以明显看出,成员函数指针是什么:没有多于或少于vtable的偏移量!在您的情况下,此偏移很可能只是零,因为{{1}}是vtable中的第一个也是唯一的条目。当你调用成员函数指针后面的函数时,你给它一个查找vtable的对象。然后,vtable的偏移量(成员函数指针)用于加载指向执行的实际代码的指针,就像对虚函数的任何其他调用一样。
唯一的区别是,在正常的虚函数调用中,当您使用成员函数指针时,编译器将知道通过您调用的函数的名称查找函数指针的精确偏移量,该偏移量由成员函数指针在运行时提供。