gpt4 book ai didi

c++ - 避免对象切片

转载 作者:太空宇宙 更新时间:2023-11-04 15:11:44 25 4
gpt4 key购买 nike

所以我正在刷新 C++,老实说,它已经有一段时间了。我制作了一个控制台乒乓球游戏作为一种复习任务,并获得了一些关于对我的类使用多态性以派生自基本“GameObject”(具有一些将对象绘制到屏幕的基本方法)的输入。

其中一个输入是(我随后询问过)从基类派生时内存是如何工作的。因为我并没有真正做过很多高级 C++。

例如,假设我们有一个基类,现在它只有一个“绘制”方法(顺便说一句,为什么我们需要为它说 virtual?),因为所有其他派生对象实际上只共享一个公共(public)方法, 正在绘制:

class GameObject
{
public:

virtual void Draw( ) = 0;
};

例如我们还有一个球类:

class Ball : public GameObject

我收到的信息是,在适当的游戏中,这些可能会保存在某种游戏对象指针 vector 中。像这样:std::vector<GameObject*> _gameObjects;

(所以是指向游戏对象的指针 vector )(顺便说一句,为什么我们要在这里使用指针?为什么不只是纯游戏对象?)。我们将使用以下内容实例化这些游戏对象之一:

_gameObjects.push_back( new Ball( -1, 1, boardWidth / 2, boardHeight / 2 ); ); 

(new 返回指向对象的指针是否正确?IIRC)。根据我的理解,如果我尝试做类似的事情:

Ball b;
GameObject g = b;

事情会变得一团糟(如此处所示:What is object slicing?)

但是...当我执行 new Ball( -1, 1, boardWidth / 2, boardHeight / 2 ); 时,我不是简单地自己创建 Derived 对象吗?还是自动将其分配为游戏对象?我无法真正弄清楚为什么一个有效而另一个无效。它与通过 new 创建对象有关吗?对比 Ball ball例如?

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

最佳答案

基本问题是复制对象(这在类是“引用类型”的语言中不是问题,但在 C++ 中默认是按值传递事物,即制作拷贝)。 “切片”是指将较大对象(B 类型,派生自 A)的值复制到较小对象(A 类型)中).因为 A 较小,所以只制作了部分拷贝。

当你创建一个容器时,它的元素是它们自己的完整对象。例如:

std::vector<int> v(3);  // define a vector of 3 integers
int i = 42;
v[0] = i; // copy 42 into v[0]

v[0] 是一个 int 变量,就像 i 一样。

类也会发生同样的事情:

class Base { ... };
std::vector<Base> v(3); // instantiates 3 Base objects
Base x(42);
v[0] = x;

最后一行将 x 对象的内容复制到 v[0] 对象中。

如果我们像这样改变 x 的类型:

class Derived : public Base { ... };
std::vector<Base> v(3);
Derived x(42, "hello");
v[0] = x;

... 然后 v[0] = x 尝试将 Derived 对象的内容复制到 Base 对象中。在这种情况下发生的是所有在 Derived 中声明的成员都被忽略。仅复制在基类 Base 中声明的数据成员,因为这是所有 v[0] 的空间。

指针给你的是避免复制的能力。当你做的时候

T x;
T *ptr = &x;

ptr不是x的拷贝,它只是指向x

同样,你可以这样做

Derived obj;
Base *ptr = &obj;

&objptr 有不同的类型(分别是 Derived *Base *),但是 C++ 允许这段代码无论如何。因为 Derived 对象包含 Base 的所有成员,所以可以让 Base 指针指向 Derived 实例。

这给你的本质上是一个简化的 obj 接口(interface)。当通过ptr访问时,它只有在Base中声明的方法。但是因为没有进行复制,所以所有数据(包括 Derived 特定部分)仍然存在并且可以在内部使用。

至于virtual:通常,当你通过Base类型的对象调用方法foo时,它会调用恰好 Base::foo(即在 Base 中定义的方法)。即使调用是通过一个指针进行的,该指针实际上指向派生对象(如上所述)并具有不同的方法实现,也会发生这种情况:

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

通过将 foo 标记为 virtual,我们强制方法调用使用对象的实际类型,而不是调用所通过的指针的声明类型:

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

virtual 对普通对象没有影响,因为声明类型和实际类型始终相同。它只会影响通过指向对象的指针(和引用)进行的方法调用,因为它们能够引用其他对象(可能不同类型)。

这是存储指针集合的另一个原因:当您有几个不同的 GameObject 子类时,它们都实现了自己的自定义 draw 方法,您需要注意对象的实际类型的代码,因此在每种情况下都会调用正确的方法。如果 draw 不是虚拟的,您的代码将尝试调用不存在的 GameObject::draw。根据您对其编码的精确程度,这要么不会首先编译,要么在运行时中止。

关于c++ - 避免对象切片,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56464702/

25 4 0
文章推荐: c++:使用模板将任何lambda包装在另一个lambda中
文章推荐: java - @GenerateValue 注解,构造函数值
文章推荐: html - 如何为div设置动态高度
文章推荐: java - 过滤器将 ArrayList 添加到 HashMap