【问题标题】:How virtual is this?这有多虚拟?
【发布时间】:2010-11-22 22:16:57
【问题描述】:

你能解释一下为什么吗:

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()";
    }
};

谢谢, 埃塔姆。

【问题讨论】:

    标签: c++ methods polymorphism virtual


    【解决方案1】:

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

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

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

    Child c;
    Parent& p = c;
    p.Method(); // Prints "Child::Method"
    

    【讨论】:

    • 我确信切片不是这种情况下症状的原因。
    • 对这个问题的任何答案都必须包含关于多态性如何仅适用于指针和引用的概念解释,而在第二种情况下,'p' 两者都不是。通过谈论切片,您并没有提供“真实”的答案。这是正在发生的事情的副作用,但没有回答最初的问题。
    • 我和@konrad 在一起。真正的原因不是因为您没有指针或引用。但真正的原因是因为对象不是Child 类型。这是由切片引起的。
    【解决方案2】:

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

    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()。

    【讨论】:

    • 你已经评论了发生的事情,这就是 OP 所说的。你能解释一下为什么会这样吗?
    • 那是因为调用虚方法是根据实际的对象类型,而不是指针类型。在第一种情况下,对象是 Child 类类型,在第二种情况下 - Parent 类类型。
    • @sharptooth:是的,第一种情况是通过指针调用虚方法,但是第二种情况没有虚方法调用,只有直接调用Parent::Method()
    • 当然,并且 Parent::Method() 被调用。有什么问题?
    • @sharptooth:问题是 为什么 调用了 Parent::Method()。我确定答案是它不是对虚拟方法的调用,而是直接调用。
    【解决方案3】:

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

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

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

    【讨论】:

    • 就像 OP 中的指针版本一样,您的 Parent& 实际上只是一个指针,因此不会复制 Child 的任何位,仅复制其地址。 p.Method() 调用现在是多态的。
    • 不过,这确实是个糟糕的代码,因为它泄漏了无法以可靠方式检索的内存(当然,您可以调用delete &amp;p,但这是非标准的代码)。
    • @Konrad:我们可以暂时不考虑内存泄漏吗?
    • @Konrad,这是完全标准的代码。这没有什么问题 - 除非 Parent 重载 operator& 并且它返回一些时髦的东西 - 但我认为情况并非如此。
    • @litb:为了清楚起见,我在这里对new 的使用提出了质疑,它分配了堆空间。但由于它只是暂时的,地址会丢失,并且无法以符合标准的方式永远检索。将如何释放?
    【解决方案4】:

    虚拟行为仅在虚拟功能可用时可用 通过指针或引用调用。当你说:

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

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

    【讨论】:

    • @Neil:没有指针或引用,虚拟方法将被很好地调用。在堆栈上声明一个 Child,它的虚拟方法将被调用。
    • 虚拟方法会被调用,但你不会得到虚拟行为。
    • @Michael:是的,将调用虚拟方法,但正如 Neil 所说(我只是重新表述),您不会得到多态行为,因为在这种情况下不会查找 vtable。
    【解决方案5】:

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

    【讨论】:

    • 本来以为是切片,没想到是这个机制。它根本不是多态调用。
    • 据我所知,当派生类的实例被复制为基类时,就会发生切片。通常在不接受引用或指针作为函数参数时发生,但这基本上是同一回事。如果我错了,请纠正我。
    • @quamrana:这不是多态调用,因为 Parent 不是派生类型。一个 Child 是通过 new 创建的。在堆栈上创建父级,然后将其设置为等于子级(即,子级被切分成父级)。然后在 Parent 中调用一个方法。哦,新孩子的记忆已经泄露了。
    • @Michael:它不是多态调用,因为不涉及指针(或引用),而不是因为切片。我很确定 v-table 会被复制,但它没有被使用,因为 C++ 确定类型是 Parent 并且它不必通过 v-table 进行方法调用。
    【解决方案6】:

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

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

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

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

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

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

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

    【讨论】:

      【解决方案7】:

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

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

      希望这会有所帮助。

      【讨论】:

        【解决方案8】:

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

        【讨论】:

        • “C++ 中的多态性只能通过指针实现。” ……和参考资料。哪些是 not 指针。
        【解决方案9】:

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

        【讨论】:

          【解决方案10】:

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

          p->Method();
          

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

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

          p.Method();
          

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

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

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-03-23
            • 2014-11-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-11-21
            • 2015-09-24
            • 1970-01-01
            相关资源
            最近更新 更多