关于c ++:函数重载和虚方法表

Function overloading and virtual method table

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
What is the slicing problem in C++?

我有一个简单的代码作为多态性和继承的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class A
{
public:
    int fieldInA;

    void virtual overloadedFunc()
    {
        printf("You are in A
"
);
    }
};

class B : public A
{
public:
    int fieldInB;

    void overloadedFunc()
    {
        printf("You are in B
"
);
    }
};

void DoSmth(A* a)
{
    a->overloadedFunc();
}

void DoSmthElse(A a)
{
    a.overloadedFunc();
}
int _tmain(int argc, _TCHAR* argv[])
{
    B *b1 = new B();
    B b2;
    //Breakpoint here
    DoSmth(b1);
    DoSmthElse(b2);
    scanf("%*s");
    return 0;
}

当我在断点处停止时,b1的_vptr[0]和b2的_vptr[0]的值相同(someaddr(b::overloadedfunc(void))。在dosmth()中传递b1作为参数后,局部变量a的vptr[0]仍然是someaddr(b::overloadedfunc(void)),但dosmthelse中a的vptr[0]现在是someaddr(a::overloadedfunc(void))。我确信这是我对函数重载概念的一些误解,但我不明白,为什么在第一种情况下我看到"你在B"而在第二种情况下看到"你在A"。a*b1=new b();dosmth(b1);//你在b,为什么?


首先,你需要正确使用你的术语!您没有重载任何函数,您超越了它们:

  • 重载意味着您具有相同的函数名和不同类型的参数。选择正确的重载是一个编译时操作。
  • 重写意味着你有一个多态的类层次结构(C++中的EDCOX1,24个)函数,并且用一个适用于一个更专业的对象的函数来替换被调用的函数。你超越了原来的意义。选择正确的覆盖是一个运行时操作,在C++中使用类似于虚拟函数表的操作。

这些术语足够混乱,甚至使事情变得更糟,它们甚至相互作用:在编译时,选择正确的重载,最终调用虚拟函数,从而可能被重写。此外,重写派生类可能会隐藏从基类继承的重载。不过,如果你不能弄清楚这些条款,所有这些都可能毫无意义!

现在,对于您的实际问题,当您调用DoSmthElse()时,您通过值将对象b2传递给一个接受A类型对象的函数。这将通过复制B对象的A子对象来创建A类型的对象。但由于B源于A,并不是所有的B都被表示出来,也就是说,你在DoSmthElse()中看到的A对象并不像B对象,而是像A对象。毕竟,它是一个A对象,而不是一个B对象!这个过程通常被称为切片:您将B对象的部分切片,这使它变得特殊。


要获得多态行为,需要在指向基类的指针或引用上调用虚拟函数,而不是基类的实例。当你调用这个函数时

1
void DoSmthElse(A a)

你通过了一个B的实例。这是传递值,因此参数是传递给它的B实例的切片副本。从本质上来说,这意味着A所共有的B的所有属性都保存在本副本中,而B的所有特定于B而非A的属性都将丢失。因此,在DoSmthElse()中调用函数overloadedFunc()的对象现在只属于A类型(不再属于B类型),因此当然调用A::overloadedFunc()

DoSmth的第一种情况下,当参数是指向基类的指针类型时,如您所期望的那样获得多态行为—B*参数被传递给函数,并复制一个现在属于A*类型的指针。尽管副本已被强制转换为A*,但指向的对象仍然是B类型。由于指向的对象是B类型,因此通过指向它的基类的指针访问它,可以确保当行

1
a->overloadedFunc();

执行,因为虚拟函数overloadFunc()被类B重写。如果类B没有实现,它自己的不同版本的基类虚拟函数(即类B重写类A的功能),那么将调用基类版本。