C ++被这段代码混淆了多态,指针和对象切片

时间:2017-08-19 16:28:32

标签: c++ pointers polymorphism override object-slicing

我试图理解多态,对象切片和指针在这段代码中是如何工作的。我在Visual Studio工作。

#include <iostream>

class Man {
public:

    virtual void speak() { std::cout << "I'm a man." << "\n"; }

private:

};

class Soldier : public Man {
public:

    virtual void speak() { std::cout << "I'm a soldier." << "\n"; }

private:

};

int main() {

    Man man1;
    Soldier soldier1;

    man1 = soldier1;
    std::cout << "Man1: "; man1.speak();

    Man *man2 = new Man;
    Man *soldier2 = new Soldier;

    man2 = soldier2;
    std::cout << "Man2: "; (*man2).speak();

    Man *man3 = new Man;
    Soldier *soldier3 = new Soldier; // "Man *soldier3 = new Soldier;" will give the same output.

    *man3 = *soldier3;
    std::cout << "Man3: "; man3->speak();

    return 0;

}

输出结果为:

Man1: I'm a man.
Man2: I'm a soldier.
Man3: I'm a man.

我做了一些搜索并了解了这个概念&#34;对象切片&#34;。我猜这就是man1和man3发生的事情。但是等等,我们没有使用关键字&#34;虚拟&#34;在所有这些课程?不应该man1和* man3首先找出它们各自的类对象,并调用特定的覆盖说()?

或者是因为切片已经发生在=运算符处,在行:

man1 = soldier1;

这一行:

*man3 = *soldier3;

man1和* man3现在真的只是Man对象吗?

一位编码员猜测这是因为=运算符只是将右侧值分配给左侧变量的副本。它需要得到确认。

除此之外,我想要实现的目标是将Soldier对象复制到不同的内存地址,这与我在同一个内存地址指向两个指针的情况不同,对于man2和soldier2。

最后,我想知道为什么在第2部分中没有发生切片,以及在这样的语法中真正发生了什么:

Man *soldier2 = new Soldier;

说真的是做什么..?

我很欣赏这方面的任何见解。我是一名基本的C ++程序员:)&lt;

3 个答案:

答案 0 :(得分:3)

  

我们没有使用关键字&#34; virtual&#34;在所有这些课程中

virtual关键字与对象切片无关。

  

不应该man1和* man3首先找出它们各自的类对象,并调用特定的覆盖说()?

不,他们不能,他们不应该。 C ++有一定的对象模型。它包括占据特定内存区域的对象。在这个模型中,对象分配没有地方像你期望的那样工作。

考虑对士兵类的这种修改:

class Soldier : public Man {
public:

    virtual void speak() { std::cout << "I'm a soldier with a" << weapon << "\n"; }

    Soldier (const std::string& w) : weapon(w) {}

private:

    std::string weapon;

};

现在,Soldier个对象占用的空间比Man个对象多。分配

man1 = soldier1

man1内根本没有适合武器弦的位置,所以它被切断了。现在士兵speak无法工作,因为它无法找到武器,因此使用了人speak

当你指定指针时,没有切断,因为Man指针完全能够指向Soldier。在原始内存级别,所有指针基本相同,并且他们不关心要指向的内容。这就是多态可以发挥作用的原因。

有些人(包括我自己)会争辩说,由于对象切片几乎总是一个错误,因此尝试调用它应该会导致编译时错误而不是混淆静默破坏。但是这种语言目前还没有以这种方式定义,所以你必须要小心。

答案 1 :(得分:1)

  

或者是因为切片已经发生在=运算符处,在行:...

确实在man1 = soldier1处切片,但这并不影响man3 / soldier3的情况。在这种情况下切片与使用man3 / soldier3取消引用指针时完全相同。见下文。

当您说man2 = soldier2时,您说&#34;将存储在指针man1中的地址设置为存储在soldier1&#34;中的地址。这不会导致地址切片,man1指向的内存位置现在是士兵。

但是,当你说*man3 = *soldier3时,你告诉计算机将内存中man3的值分配给存储在soldier3中的值。换句话说,你说&#34;取存储在soldier3指向的内存位置的值,并将其存储在man3指向的内存位置。&#34; (顺便说一句,这被称为&#34;解除引用&#34;指针)。问题是存储一名男子所需的记忆太小而无法容纳一名士兵,因为士兵是一名男子加上一些其他数据。因此,在将新士兵数据存储在man内存位置之前,编译器裁减。

  

最后,我想知道为什么在第2部分中没有发生切片,以及在这样的语法中真正发生了什么:

这不会导致切片的原因主要是因为C ++被设计为使用指针以这种方式工作。所有指针的实际大小在给定的体系结构上是相同的,并且编译器知道士兵&#34;是&#34;人。因为您将函数声明为虚拟,所以编译器知道对该函数使用正确的覆盖。

答案 2 :(得分:0)

用两个词:只有拥有Soldier类的实例时才会调用Soldier虚拟成员函数。您可以通过Man类指针或指向Soldier对象的引用来访问它。将Soldier变量赋值或复制到Man变量时,只复制基类Man,因为目标变量不能容纳Soldier类。