避免对象切片 [英] Avoiding object slicing
问题描述
所以我正在刷新C ++,说实话已经有一段时间了.我做了一个控制台式乒乓球游戏,作为一种复习任务,并获得了一些关于将多态用于我的类的信息,以从基础"GameObject"(具有一些将对象绘制到屏幕上的基本方法)派生而来.
So I am refreshing on C++, and honestly it's been awhile. I made a console pong game as a sort of refresher task and got some input on using polymorphism for my classes to derive from a base "GameObject" (that has some base methods for drawing objects to the screen).
输入的其中一项是(后来我问了)是从基类派生时内存如何工作的.因为我还没有真正做过很多高级C ++.
One of the pieces of input was (and I had subsequently asked about) was how memory worked when deriving from base classes. Since I hadn't really done much advanced C++.
例如,假设我们有一个基类,现在它只有一个"draw"方法(顺便说一句为什么我们要说virtual
?),因为所有其他派生对象实际上只共享一个通用方法. ,并且正在绘制:
For instance lets say we have a base class, for now it just has a "draw" method (Btw why do we need to say virtual
for it?), since all other derived objects really only share one common method, and that's being drawn:
class GameObject
{
public:
virtual void Draw( ) = 0;
};
例如,我们还有一个球类:
we also have a ball class for instance:
class Ball : public GameObject
我收到的输入是,在适当的游戏中,这些内容可能会保留在GameObject指针的某种矢量中.像这样的东西:std::vector<GameObject*> _gameObjects;
The input I received is that in a proper game these would probably be kept in some sort of vector of GameObject pointers. Something like this: std::vector<GameObject*> _gameObjects;
(所以是指向GameObjects的指针的向量)(顺便说一句,为什么我们在这里使用指针?为什么不只是纯GameObjects?).我们将使用以下示例实例化这些游戏对象之一:
(So a vector of pointers to GameObjects) (BTW Why would we use pointers here? why not just pure GameObjects?). We would instantiate one of these gameObjects with something like:
_gameObjects.push_back( new Ball( -1, 1, boardWidth / 2, boardHeight / 2 ); );
(new
返回指向正确对象的指针吗?IIRC).根据我的理解,如果我尝试执行以下操作:
(new
returns a pointer to the object correct? IIRC). From my understanding if I tried to do something like:
Ball b;
GameObject g = b;
事情会变得混乱(如此处所示:什么是对象切片?)
That things would get messed up (as seen here: What is object slicing?)
但是...我在执行new Ball( -1, 1, boardWidth / 2, boardHeight / 2 );
时不是简单地自己创建派生对象,还是会自动将其分配为GameObject吗?我真的无法弄清楚为什么一个有效而另一个无效.它与通过new
而不是例如Ball ball
创建对象有关吗?
However...am I not simply creating Derived objects on their own when I do the new Ball( -1, 1, boardWidth / 2, boardHeight / 2 );
or is that automatically assigning it as a GameObject too? I can't really figure out why one works and one doesn't. Does it have to do with creating an object via new
vs just Ball ball
for example?
很抱歉,如果这个问题没有意义,我只是想了解如何进行此对象切片.
Sorry if the question makes no sense, im just trying to understand how this object slicing would happen.
推荐答案
基本问题是复制对象(在类为引用类型"的语言中,这不是问题,但在C ++中,默认值是通过值,即制作副本). 切片"是指将较大的对象(从A
派生的类型B
)的值复制到较小的对象(A
类型).因为A
较小,所以只能复制部分副本.
The fundamental issue is copying an object (which is not an issue in languages where classes are "reference types", but in C++ the default is to pass things by value, i.e. making a copy). "Slicing" means copying the value of a bigger object (of type B
, which derives from A
) into a smaller object (of type A
). Because A
is smaller, only a partial copy is made.
创建容器时,其元素是它们自己的完整对象.例如:
When you create a container, its elements are full objects of their own. For example:
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
.
类也会发生同样的事情:
The same thing happens with classes:
class Base { ... };
std::vector<Base> v(3); // instantiates 3 Base objects
Base x(42);
v[0] = x;
最后一行将x
对象的内容复制到v[0]
对象中.
The last line copies the contents of the x
object into the v[0]
object.
如果我们这样更改x
的类型:
If we change the type of x
like this:
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]
可以容纳的空间.
... then v[0] = x
tries to copy the contents of a Derived
object into a Base
object. What happens in this case is that all members declared in Derived
are ignored. Only the data members declared in the base class Base
are copied, because that's all v[0]
has room for.
指针为您提供避免复制的功能.当你做
What a pointer gives you is the ability to avoid copying. When you do
T x;
T *ptr = &x;
,ptr
不是x
的副本,它只是指向x
.
, ptr
is not a copy of x
, it just points to x
.
类似地,您可以
Derived obj;
Base *ptr = &obj;
&obj
和ptr
具有不同的类型(分别为Derived *
和Base *
),但是C ++仍然允许该代码.因为Derived
对象包含Base
的所有成员,所以可以让Base
指针指向Derived
实例.
&obj
and ptr
have different types (Derived *
and Base *
, respectively), but C++ allows this code anyway. Because Derived
objects contain all members of Base
, it's OK to let a Base
pointer point at a Derived
instance.
这实际上为您提供了obj
的简化接口.通过ptr
访问时,仅具有在Base
中声明的方法.但是因为没有进行复制,所以所有数据(包括Derived
特定部分)仍然存在并且可以在内部使用.
What this gives you is essentially a reduced interface to obj
. When accessed through ptr
, it only has the methods declared in Base
. But because no copying was done, all data (including the Derived
specific parts) are still there and can be used internally.
与virtual
一样:通常,当您通过类型为Base
的对象调用方法foo
时,它将完全调用Base::foo
(即Base
中定义的方法).即使通过使用方法的不同实现实际指向派生对象的指针进行调用(如上所述),也会发生这种情况:
As for virtual
: Normally, when you call a method foo
through an object of type Base
, it will invoke exactly Base::foo
(i.e. the method defined in Base
). This happens even if the call is made through a pointer that actually points at a derived object (as described above) with a different implementation of the method:
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
,我们强制方法调用使用对象的实际类型,而不是通过以下方式进行调用的声明的指针类型:
By marking foo
as virtual
, we force the method call to use the actual type of the object, instead of the declared type of the pointer the call is made through:
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
对普通对象无效,因为那里的声明类型和实际类型始终相同.它仅影响通过指向对象的指针(和引用)进行的方法调用,因为这些对象具有引用其他对象(可能具有不同类型)的能力.
virtual
has no effect on normal objects because there the declared type and the actual type are always the same. It only affects method calls made through pointers (and references) to objects, because those have the ability to refer to other objects (of potentially different types).
这是存储指针集合的另一个原因:当您有几个GameObject
的不同子类,所有这些子类都实现了自己的自定义draw
方法时,您希望代码注意实际的类型.对象,因此在每种情况下都会调用正确的方法.如果draw
不是虚拟的,则您的代码将尝试调用不存在的GameObject::draw
.视您的编码方式而定,它要么不会首先编译,要么会在运行时中止.
And that is another reason to store a collection of pointers: When you have several different subclasses of GameObject
, all of which implement their own custom draw
method, you want the code to pay attention to the actual types of the objects, so the right method gets called in each case. If draw
weren't virtual, your code would attempt to invoke GameObject::draw
, which doesn't exist. Depending on how exactly you code it, this either wouldn't compile in the first place or abort at runtime.
这篇关于避免对象切片的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!