Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do you need pointers in this situation? [duplicate]

Possible Duplicate:
Learning C++: polymorphism and slicing

This is building off a question I asked before. The classes look like this:

class Enemy
{
    public:
        void sayHere()
        {
            cout<<"Here"<<endl;
        }
        virtual void attack()
        {
        }
};

class Monster: public Enemy
{

    public:
        void attack()
        {
            cout<<"RAWR"<<endl;
        }

};
class Ninja: public Enemy
{

    public:
        void attack()
        {

            cout<<"Hiya!"<<endl;
        }
};

I am new to C++ and I'm confused as to why this will only work with pointers (both Ninja and monster are derived from Enemy):

int main()
{
    Ninja ninja;
    Monster monster;

    Enemy *enemies[2];

    enemies[0] = &monster;
    enemies[1] = &ninja;

    for (int i = 0; i < 2; i++)
    {
        enemies[i]->attack();
    }

    return 0;
}

Why can't I instead do this?:

int main()
{
    Ninja ninja;
    Monster monster;

    Enemy enemies[2];

    enemies[0] = monster;
    enemies[1] = ninja;

    for (int i = 0; i < 2; i++)
    {
        enemies[i].attack();
    }

    return 0;
}
like image 812
Hudson Worden Avatar asked Jul 31 '11 18:07

Hudson Worden


People also ask

Why do we need to use pointers?

Pointers save memory space. Execution time with pointers is faster because data are manipulated with the address, that is, direct access to memory location. Memory is accessed efficiently with the pointers. The pointer assigns and releases the memory as well.

Why do we need double pointer?

Double pointers can also be used when we want to alter or change the value of the pointer. In general double pointers are used if we want to store or reserve the memory allocation or assignment even outside of a function call we can do it using double pointer by just passing these functions with ** arg.


2 Answers

This is a great question that hits at the heart of some of the trickier points of C++ inheritance. The confusion arises because of the difference between static types and dynamic types, as well as the way that C++ allocates storage for objects.

To begin, let's discuss the difference between static and dynamic types. Every object in C++ has a static type, which is the type of the object that is described in the source code. For example, if you try writing

Base* b = new Derived;

Then the static type of b is Base*, since in the source code that's the type you declared for it. Similarly, if you write

Base myBases[5];

the static type of myBases is Base[5], an array of five Bases.

The dynamic type of an object is the type that the object actually has at runtime. For example, if you write something like

Base* b = new Derived;

Then the dynamic type of b is Derived*, since it's actually pointing at a Derived object.

The distinction between static and dynamic types is important in C++ for two reasons:

  1. Assignments to objects are always based on the static type of the object, never the dynamic type.
  2. Invocations of virtual functions only dispatch to the dynamic type if the static type is a pointer or reference.

Let's address each of these in turn.

First, one of the problems with the second version of the code is that you do the following:

Ninja ninja;
Monster monster;

Enemy enemies[2];

enemies[0] = monster;
enemies[1] = ninja;

Let's trace through what happens here. This first creates a new Ninja and Monster object, then creates an array of Enemy objects, and finally assigns the enemies array the values of ninja and monster.

The problem with this code is that when you write

enemies[0] = monster;

The static type of the lhs is Enemy and the static type of the rhs is Monster. When determining how to do an assignment, C++ only looks at the static types of the objects, never the dynamic types. This means that because enemies[0] is statically typed as an Enemy, it has to hold something precisely of type Enemy, never any derived type. This means that when you do the above assignment, C++ interprets this to mean "take the monster object, identify just the part of it that's an Enemy, then copy that part over into enemies[0]." In other words, although a Monster is an Enemy with some extra additions, only the Enemy part of Monster will be copied over into enemies[0] with this line of code. This is called slicing, since you're slicing off part of the object and leaving behind just the Enemy base portion.

In the first piece of code that you posted, you have this:

Ninja ninja;
Monster monster;

Enemy *enemies[2];

enemies[0] = &monster;
enemies[1] = &ninja;

This is perfectly safe, because in this line of code:

enemies[0] = &monster;

The lhs has static type Enemy* and the rhs has type Monster*. C++ legally allows you to convert a pointer to a derived type into a pointer to a base type without any problems. As a result, the rhs monster pointer can be converted losslessly into the lhs type Enemy*, and so the top of the object isn't sliced off.

More generally, when assigning derived objects to base objects, you risk slicing the object. It is always safer and more preferable to store a pointer to the derived object in a pointer to a base object type, because no slicing will be performed.

There's a second point here as well. In C++, whenever you invoke a virtual function, the function is only called on the dynamic type of the object (the type of the object that the object really is at runtime) if the receiver is a pointer or reference type. That is, if you have the original code:

Ninja ninja;
Monster monster;

Enemy enemies[2];

enemies[0] = monster;
enemies[1] = ninja;

And write

enemies[0].attack();

then because enemies[0] has static type Enemy, the compiler won't use dynamic dispatch to determine which version of the attack function to call. The reason for this is that if the static type of the object is Enemy, it always refers to an Enemy at runtime and nothing else. However, in the second version of the code:

Ninja ninja;
Monster monster;

Enemy *enemies[2];

enemies[0] = &monster;
enemies[1] = &ninja;

When you write

enemies[0]->attack();

Then because enemies[0] has static type Enemy*, it can point at either an Enemy or a subtype of Enemy. Consequently, C++ dispatches the function to the dynamic type of the object.

Hope this helps!

like image 101
templatetypedef Avatar answered Sep 30 '22 18:09

templatetypedef


Without pointers, your enemies[] array represents a space on the stack sufficient to store two "Enemy" objects-- which means storing all their fields (plus perhaps overhead for a vtable pointer and alignment). Derived classes of Enemy could have additional fields and therefore be larger, so it doesn't let you store a derived object of Enemy in the space reserved for an actual Enemy object. When you do an assignment as in the example, it uses the assignment operator (in this case, defined implicitly)-- which sets the values in the left hand side object's fields to the values of the corresponding fields in the right hand side object, leaving the left-hand-side object's type (and so vtable pointer) unchanged. This is called "object slicing" and is generally to be avoided.

Pointers are all the same size, so you can put a pointer to a derived object of Enemy in the space for a pointer to Enemy and use it just as if it were a pointer to a plain enemy object. Since the pointer to a derived object points to an actual instance of the derived object, calls to virtual functions on the pointer will use the derived object's vtable and give you the desired behavior.

like image 27
antlersoft Avatar answered Sep 30 '22 20:09

antlersoft