0

可能重复:
C++ 中的切片问题是什么?

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

class A
{
public:
    int fieldInA;

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

class B : public A
{
public:
    int fieldInB;

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

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 的 _vfptr[0] 和 b2 的 _vfptr[0] 的值是相同的(SomeAddr(B::overloadedFunc(void)))。在 DoSmth() 中将 b1 作为参数传递后,局部变量 a 的 _vfptr[0] 仍然是 someAddr(B::overloadedFunc(void)),但 DoSmthElse 中 a 的 _vfptr[0] 现在是 someAddr(A::overloadedFunc(void) ))。我确定这是我对函数重载概念的一些误解,但我无法理解,为什么在第一种情况下我看到“你在 B 中”,而在第二种情况下看到“你在 A 中”。与 A *b1 = new B(); 相同 DoSmth(b1); // 你在B,为什么?

4

2 回答 2

5

首先,您需要正确使用术语!您没有重载任何功能,而是覆盖了它们:

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

这些术语令人困惑,更糟糕的是,它们甚至相互作用:在编译时选择正确的重载方式,最终调用可能被覆盖的虚函数。此外,覆盖派生类可能会隐藏从基类继承的重载。但是,如果您无法弄清楚这些条款,那么所有这些都可能没有意义!

Now, for your actual problem, when you call DoSmthElse() you pass your object b2 by value to a function taking an object of type A. This creates an object of type A by copying the A subobject of your B object. But since B is derived from A, not all of B gets represented, i.e., the A object you see in DoSmthElse() doesn't behave like a B object but like an A object. After all, it is an A object and not a B! This process is typically called slicing: you slice off the parts of the B object which made it special.

于 2012-11-21T20:36:21.543 回答
1

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

void DoSmthElse(A a)

你传递了一个B. 这是按值传递的,因此参数是您传递给它的实例的切片副本。B本质上,这意味着所有B共同的属性A都保留在此副本中,并且所有属性B都特定于B而不是A丢失。因此,调用DoSmthElse()该函数overloadedFunc()的对象现在完全属于类型A(不再属于类型B),因此当然A::overloadedFunc()会被调用。

在第一种情况下,DoSmth当参数是指向基类的指针类型时,多态行为会如您所愿地获得 -B*参数被传递给函数,并且这个指针的副本现在是 type A*。尽管副本已被强制转换为A*,但指向的对象仍然是 类型B。因为指向的对象的类型B是通过指向它的基类的指针来访问它,所以确保函数B::overloadedFunc()在行时被实际调用

a->overloadedFunc();

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

于 2012-11-21T20:02:29.183 回答