4

你能解释一下为什么:

int main (int argc, char * const argv[]) {
    Parent* p = new Child();
    p->Method();
    return 0;
}

打印“ Child::Method() ”,并且:

int main (int argc, char * const argv[]) {
    Parent p = *(new Child());
    p.Method();
    return 0;
}

打印“ Parent::Method() ”?

课程:

class Parent {
public:
    void virtual Method() {
        std::cout << "Parent::Method()";
    }
};

class Child : public Parent {
public:
    void Method() {
        std::cout << "Child::Method()";
    }
};

谢谢,埃塔姆。

4

10 回答 10

11

您的第二个代码将一个对象复制到一个变量中。通过一个称为slicing的过程,它会丢失所有特定于 的信息(即所有属于 的私有字段),因此会丢失与其关联的所有虚拟方法信息。ChildParentChildChild

此外,您的两个代码都泄漏内存(但我想您知道这一点)。

不过,您可以使用参考。例如:

Child c;
Parent& p = c;
p.Method(); // Prints "Child::Method"
于 2009-08-26T09:02:27.843 回答
9

在第一种情况下,您调用的实际对象是 Child 类:

Parent* p = new Child(); // you new'ed Child class
p->Method(); // and a method of a Child class object is getting called

这就是调用 Child::Method() 的原因。在第二种情况下,您将 Child 类对象复制到 Parent 类对象上:

Parent p = *(new Child()); // you new'ed Child, then allocated a separate Parent object on stack and copied onto it
p.Method(); // now you have a Parent object and its method is called

并调用 Parent::Method()。

于 2009-08-26T08:59:20.393 回答
3

仅当通过指针或引用调用虚函数时,虚拟行为才可用。当你说:

Parent p = *(new Child());
p.Method();

你有一个实际的 Parent 对象,所以无论你分配给 p 什么,Parent 的方法都会被调用。

于 2009-08-26T09:04:02.287 回答
3

如果你这样做会发生什么?

int main (int argc, char * const argv[]) {
    Parent &p = *(new Child());
    p.Method();
    return 0;
}

这具有相同的“句法”效果(p不需要取消引用即可使用它),但语义效果现在完全不同,因为您不再将 a 的一部分复制Child到 aParent中。

于 2009-08-26T09:05:24.530 回答
2

在您的第二个示例中,发生了切片:子实例转换为父实例,父实例没有子方法的 vtable 条目。Child-method 被“切割”掉了。

于 2009-08-26T09:01:04.710 回答
2

你的第一个案例很简单。Child 的一个实例被创建并分配给 p。所以调用 p->Method() 调用 Child::Method()。

在第二种情况下,会发生四件事:

  1. 创建由编译器分配的临时变量标识的 Child 类的实例。
  2. 创建由变量 p 标识的 Parent 类的实例。
  3. 实例化 p 时调用复制构造函数 Parent::Parent(Parent&) 以将 Child 实例的状态的 Parent 'slice' 复制到 p。请注意,如果您不定义此复制构造函数,则编译器会为您创建它
  4. 您在 p 上调用 Method(),它是Parent的一个实例。

尝试显式定义复制构造函数,您会看到它被调用。

您可能的混淆可能是因为两个示例中的分配 (=) 做了不同的事情。在第一个示例中,它只是将一个指针设置为等于另一个指针,并且只有一个对象。在第二个没有指针,所以你分配(切片)value。这会调用复制构造函数,您会得到两个对象(一个子对象和一个父对象)。编译器正在创建一个不可见的临时变量这一事实无助于理解发生了什么。您可以查看这篇关于复制构造函数的文章

如果您习惯于 Java 或 C# 等语言,其中基本上所有内容都是引用(也称为指针),那么这是一个容易犯的错误。

正如其他人所说,多态性仅适用于指针和引用,并且在您的第二个示例中未使用这些。

于 2009-08-26T10:11:11.900 回答
1

在第一种情况下,您在“子”对象上有一个“父”指针,这在某种程度上是通用的,因为“子”继承自“父”。因此调用了重载的 'Child' 方法。

在第二种情况下,您将“子”实例隐式转换为“父”类型,因此您在“父”对象上调用该方法。

希望这可以帮助。

于 2009-08-26T09:01:17.517 回答
1

C++ 中的多态性只能通过指针实现。静态类型和动态类型之间的区别(在您的第一个示例中,p 是静态类型 Parent*,但动态类型 Child*,这使 C++ 能够调用派生类的方法)对非指针不起作用.

于 2009-08-26T09:05:15.577 回答
0

在您的第一个示例中,您通过指针调用 Method:

p->Method();

在这种情况下,“p”是指向父级的指针,并且 C++ 知道父级有一个 v 表,因此它使用它来查找要调用的实际方法,在这种情况下,正如您所说,Child::Method()因为当 C++ 找到 v -table,它找到“新”子实例的 v-table。

在第二个示例中,您在实例上调用 Method:

p.Method();

在这种情况下,'p' 是 Parent 的一个实例,C++ 假设它知道确切的类型确实是 'Parent',因此它在Parent::Method()不经过任何 v 表的情况下调用。

我刚刚用 VS2008 进行了检查,上面实际上是发生了什么并且没有切片,但是,我认为切片确实发生了,但是如果你这样做了,你只会在第二种情况下看到它:

Parent& q=p;
q.Method();

然后我看到:Parent::Method()正在打印。 q.Method()必须是虚拟调用,但它只能找到 Parent 的 v-table。

于 2009-08-26T09:03:07.403 回答
0

在第一种情况下,指针 p 指向的对象的类型是 'Child' 类型。由于 child 覆盖了基类中定义的虚方法,因此调用了 child 的方法。在第二种情况下,您已将子对象复制到父对象中。这里发生了对象切片,结果对象的类型是“父”类型。因此,当您调用该方法时,将调用父级的方法。

于 2009-08-26T09:05:01.940 回答