避免对象切片 所以我对C++感到耳目一新,说实话,已经有一段时间了。我制作了一个控制台乒乓球游戏作为一种复习任务,并获得了一些关于使用多态性的信息,使我的类从一个基本的“GameObject”(它有一些将对象绘制到屏幕上的基本方法)派生而来

避免对象切片 所以我对C++感到耳目一新,说实话,已经有一段时间了。我制作了一个控制台乒乓球游戏作为一种复习任务,并获得了一些关于使用多态性的信息,使我的类从一个基本的“GameObject”(它有一些将对象绘制到屏幕上的基本方法)派生而来,c++,object-slicing,C++,Object Slicing,其中一个输入是(我后来问过)从基类派生时内存是如何工作的。因为我没有真正做过很多高级的C++。 例如,假设我们有一个基类,现在它只有一个“draw”方法(顺便问一下,为什么我们需要为它说virtual),因为所有其他派生对象实际上只共享一个公共方法,并且正在绘制: class GameObject { public: virtual void Draw( ) = 0; }; 我们还有一个ball类,例如: class Ball : public GameObject 我收到的信息是

其中一个输入是(我后来问过)从基类派生时内存是如何工作的。因为我没有真正做过很多高级的C++。 例如,假设我们有一个基类,现在它只有一个“draw”方法(顺便问一下,为什么我们需要为它说
virtual
),因为所有其他派生对象实际上只共享一个公共方法,并且正在绘制:

class GameObject
{
public:

    virtual void Draw( ) = 0;
};
我们还有一个ball类,例如:

class Ball : public GameObject
我收到的信息是,在一个适当的游戏中,这些可能会被保存在某种游戏对象指针向量中。类似这样:
std::vector\u gameObjects

(一个指向游戏对象的指针向量)(顺便问一下,为什么我们在这里使用指针?为什么不仅仅是纯游戏对象?)。我们将用以下内容实例化其中一个游戏对象:

_gameObjects.push_back( new Ball( -1, 1, boardWidth / 2, boardHeight / 2 ); ); 
Ball b;
GameObject g = b;
new
返回指向正确对象的指针?IIRC)。根据我的理解,如果我尝试做以下事情:

_gameObjects.push_back( new Ball( -1, 1, boardWidth / 2, boardHeight / 2 ); ); 
Ball b;
GameObject g = b;
事情会变得一团糟(如图所示:)

然而,当我做
新球(-1,1,boardWidth/2,boardHeight/2)时,我不是简单地自己创建派生对象吗或者这会自动将其指定为游戏对象吗?我真的搞不懂为什么一个能行,一个不行。例如,它是否与通过
new
vs仅仅
Ball
创建对象有关


对不起,如果这个问题没有意义,我只是想了解这个对象切片是如何发生的

直接将对象存储在容器中时会发生对象切片。存储指向对象的指针(或更好的智能指针)时,不会发生切片。因此,如果你将
存储在
向量
中,它将被切分,但是如果你将
球*
存储在
向量
中,一切都会好起来。

你的问题的快速答案是,当你执行
游戏对象时,对象切分不是问题。推回(新球(…)
因为
new
球大小的对象分配了足够的内存

这是解释。对象切片是编译器认为对象比实际对象小的问题。因此,在您的代码示例中:

Ball b;
GameObject g = b;
编译器已经为名为
g
GameObject
预留了足够的空间,但您正在尝试将
球(
b
)放在那里。但是一个
可能比一个
游戏对象
大,然后数据就会丢失,坏东西可能会开始发生

但是,当您执行
新建球(…)
新建游戏对象(…)
时,编译器确切地知道要分配多少空间,因为它知道对象的真实类型。然后,您存储的实际上是一个
球*
游戏对象*
。您可以安全地将
球*
存储在
游戏对象*
类型中,因为指针大小相同,因此不会发生对象切片。指向的内存可以是任意数量的不同大小,但指针的大小始终相同

顺便问一下,为什么我们要说它是虚拟的

如果未将函数声明为虚拟函数,则无法使用虚拟分派调用该函数。当通过指向基类的指针或引用虚拟地调用函数时,调用将被分派到最派生类(如果存在)中的重写。换句话说,
virtual
允许运行时多态性

如果函数是非虚拟的,则只能静态地调度函数。静态调用函数时,将调用编译时类型的函数。因此,如果通过基指针静态调用函数,则调用基函数,而不是派生重写

顺便问一下,我们为什么要在这里使用指针?为什么不仅仅是纯粹的游戏对象

GameObject
是一个抽象类,因此您不能拥有该类型的具体对象。因为你不能有一个具体的
游戏对象
,你也不能有它们的数组(或向量)<代码>游戏对象
实例只能作为派生类型的基类子对象存在

new
返回指向对象的指针是否正确

new
在动态存储器中创建一个对象,并返回指向该对象的指针

顺便说一下,如果在丢失指针值之前未能对指针调用
delete
,则内存泄漏。哦,如果您尝试两次
删除
某些内容,或者
删除
非源于
新建
的内容,那么程序的行为将是未定义的。内存分配很困难,您应该始终使用智能指针来管理它。在您的示例中,使用裸指针向量是一个非常糟糕的主意

此外,通过基对象指针删除对象具有未定义的行为,除非基类的析构函数是虚拟的。
GameObject
的析构函数不是虚拟的,因此您的程序无法避免UB或内存泄漏。两种选择都不好。解决方案是使
GameObject
的析构函数虚拟化

避免对象切片

通过使基类抽象,可以避免意外的对象切片。由于抽象类不可能有具体实例,因此不能意外地“切掉”派生对象的基

例如:

Ball b;
GameObject g = b;
std::vector<int> v(3);  // define a vector of 3 integers
int i = 42;
v[0] = i;  // copy 42 into v[0]
由于
GameObject
是一个抽象类,因此格式不正确。编译器可能会这样说:

main.cpp: In function 'int main()':
main.cpp:16:20: error: cannot allocate an object of abstract type 'GameObject'
 GameObject g = b;
                ^
main.cpp:3:7: note:   because the following virtual functions are pure within 'GameObject':
 class GameObject
       ^~~~~~~~~~
main.cpp:7:18: note:    'virtual void GameObject::Draw()'
     virtual void Draw( ) = 0;
                  ^~~~
main.cpp:16:16: error: cannot declare variable 'g' to be of abstract type 'GameObject'
     GameObject g = b;
class Derived : public Base { ... };
std::vector<Base> v(3);
Derived x(42, "hello");
v[0] = x;

我将尝试回答您提出的各种问题,尽管其他人的回答中可能会有更技术性的解释
T x;
T *ptr = &x;
Derived obj;
Base *ptr = &obj;
class Base {
    public:
    void foo() const { std::cout << "hello from Base::foo\n"; }
};

class Derived : public Base {
    public:
    void foo() const { std::cout << "hello from Derived::foo\n"; }
};

Derived obj;
Base *ptr = &obj;
obj.foo();  // calls Derived::foo
ptr->foo();  // calls Base::foo, even though ptr actually points to a Derived object
class Base {
    public:
    virtual void foo() const { std::cout << "hello from Base::foo\n"; }
};

class Derived : public Base {
    public:
    void foo() const { std::cout << "hello from Derived::foo\n"; }
};

Derived obj;
Base *ptr = &obj;
obj.foo();  // calls Derived::foo
ptr->foo();  // also calls Derived::foo