C++ 被此代码与多态性、指针和对象切片混淆

C++ Confused by this code with polymorphism, pointers and 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.

我进行了一些搜索并了解了这个概念 "object slicing"。我想这就是 man1 和 man3 发生的情况。但是等等,我们不是在所有这些 class 中都使用了关键字 "virtual" 吗? man1 和 *man3 难道不应该首先找出它们各自是什么 class 对象,然后调用特定的重写 speak() 吗?

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

man1 = soldier1;

还有一行:

*man3 = *soldier3;

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

一位同事猜测这是因为 = 运算符仅将右侧值分配给左侧的 copy多变的。还需要进一步确认。

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

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

Man *soldier2 = new Soldier;

说真的,它有什么作用..?

我很感激任何对此的见解。我是一个基本的 C++ 程序员 :) <

两个词:Soldier 虚拟成员函数仅在您拥有 Soldier class 实例时被调用。您可以通过 Man class 指针或指向 Soldier 对象的引用来访问它。当您将 Soldier 变量赋值或复制到 Man 变量时,只会复制 base class Man,因为目标变量无法容纳 Soldier class.

Or is it because slicing has already happened at the = operator, at the line: ...

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

当您说 man2 = soldier2 时,您说的是 "set the address stored in the pointer man1 to the address stored in soldier1"。这不会导致地址切片,man1 指向的内存位置现在是一个士兵。

但是,当您说 *man3 = *soldier3 时,您是在告诉计算机将内存中 man3 的值分配给 soldier3 中存储的值。换句话说,你说的是 "Take the value stored in the memory location pointed to by soldier3, and store it in the memory location pointed to by man3." (顺便说一句,这叫做 "dereferencing" 指针)。问题是存储一个人所需的内存太小,无法容纳一个士兵,因为一个士兵就是一个人加上一些其他数据。因此,编译器 切片 新士兵数据,然后再将其存储在 man 内存位置。

Finally, I wonder why slicing doesn't happen in part2, and what really happens at syntax like this:

这不会导致切片的原因基本上是因为 C++ 设计为以这种方式使用指针。在给定的体系结构上,所有指针的实际大小都是相同的,编译器知道一个士兵 "is a" 人。因为您将函数声明为虚函数,所以编译器知道对该函数使用正确的覆盖。

didn't we use the keyword "virtual" in all of those classes

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

Shouldn't man1 and *man3 first find out what class of object they each are, and call the specific overriding speak()?

不,他们不能也不应该。 C++有一定的对象模型。它包括占用特定内存区域的对象。在此模型中,对象分配无法像您预期的那样工作。

考虑对士兵进行此修改class:

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 个对象占用的 space 个对象比 Man 个对象多。当你赋值

man1 = soldier1

man1 里面根本没有地方放武器绳,所以它被切断了。现在士兵的speak不可能工作,因为找不到武器,所以用男人的speak

当您分配指针时,没有中断,因为 Man 指针完全能够指向 Soldier。在原始内存级别,所有指针本质上都是相同的,它们不关心指向什么。这就是多态性可以起作用的原因。

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