views:

556

answers:

6

I've created a class, called vir, with a function move:

class vir
{
public:
     vir(int a,int b,char s){x=a;y=b;sym=s;}
     void move(){}
};

(It's derived from a class with variables int x, int y, and char sym) I have derived a class from this, called subvir:

class subvir:public vir
{
public:
     subvir(int a,int b,char s){x=a;y=b;sym=s;}
     void move();
};
subvir::move()
{
     x++;
     return;
}

And then I created an array of vir, and put a subvir into it

subvir sv1(0,0,'Q');
vir vir_RA[1]={sv1};

But when I try to use sv1.move():

vir_RA[0].move();

It uses the vir move ({}) rather than the subvir move ({x++}). I have tried making sv1 a vir and vir_RA a vir, and it works, and it also works when I make them both subvir, but I need them to be different. I tried making vir::move() a pure virtual, but then I get an error substantiating the array. Does anyone know how I can get move() to work when I use it from the array?

+4  A: 

You need an array of pointers in this case, rather than an array of instances. Use vir*[] instead of vir[]

Scott Wisniewski
Thanks, that really helps me a lot.
Keand64
+8  A: 

You are running into a problem called slicing. Use an array of pointers, or something like Boost.ptr_container.

rlbond
Slicing is not happening here. (Not yet anyway.) move() is not a virtual function so a vir* will call vir::move()
jmucchiello
@jmucchiello: Slicing is definitely happening here -- the 1st element of vir_RA will be copy-initialised (in effect, copy-constructed) from sv1 using the compiler's autogenerated copy constructor for type vir.
j_random_hacker
+5  A: 

Two things. The array is an array of vir's so of course it uses the vir::move. move() is not a virtual method.

But more important is slicing. You cannot put subclasses into an array. If sizeof vir != sizeof subvir, the array will not line up correctly. Currently they are the same size. But what happens if they aren't.

jmucchiello
+8  A: 

The base class must have virtual functions to get you what you want, making these pure will result in an abstract base class -- something you cannot instantiate. However, you can still create pointers/references to abstract base classes and assign derived class objects to them. Your base class is best represented as:

class vir
{
public:
     vir(int a,int b,char s){x=a;y=b;sym=s;}
     virtual void move(){}
};

This makes the derived class's move virtual as well. However your move definition lacks a return value and will not compile. Try:

void subvir::move()
{
     x++;
     return;
}

Note that you need either pointers (as mentioned in the other answers) or references to derived classes for dynamic binding to work. So, instead of an array of vir objects, use an array of base class pointers:

vir* v[ 2 ] = { new subvir(0, 0, 'Q'), new subvir(10, -10, 'P') };

You should also f Do read up on the following sections of the C++ FAQ Lite:

dirkgently
The last snippet is a source of errors: you a storing the address of two temporary objects in the array. By the end of the instruction (the semi-colon) both temporaries will be destroyed and the array will held pointers to nowhere.
David Rodríguez - dribeas
True. What I really had in mind was `new subvir...` -- updated my answer.
dirkgently
+2  A: 

Yes, basically compiler does not allow subclasses in arrays because arrays are initialized tightly for the type size, and subtypes tend to be larger than the parents and it would lead to problems if you could initialize arrays with subtype values. What really happens is compiler first allocates array N * size(base_type) bytes. And then it copies size(base_type) bytes of each of the initialization objects. if they were of different types, they would get truncated, and weird things could happen in your code.

Nikola Jevtic
j_random_hacker
+1  A: 

Let me consolidate the previous answers.

There are actually two issues here. One is slicing. You are initializing an array of virs with a copy of a subvir. In such cases the compiler slices the vir part out of the subvir and copies it into the array, so you really do get only vir objects there. Now in your particular case, subvir has no additional data members beyond those of vir, so slicing is somewhat degenerate and the vir object looks a lot like a subvir one. However, vir and subvir are different classes and the object in the array ends up being a vir object and not a subvir object disguised as vir. One way the difference between the two would manifest practically, even if both had the same data members, is if vir had virtual functions overloaded by subvir. In that case the vtable pointer in the object in the array would point to vir's vtable, not subvir's. Of course, it would be even more explicit if subvir were to contain additional data members not found in vir.

The second issue is polymorphism. At the point of usage (the call to move()) the compiler thinks you are calling the move() method of an object of type vir (since the array is an array of virs). (The compiler is of course correct in thinking so due to the slicing, degenerate as it may be in this case.) Had it actually been a subvir object as you intented, you could get subvir::move() called by making move() virtual in vir.

To get the desired behavior you could use an array of pointers (but then you would be operating directly on sv1, not a copy of it, unless you first created a copy and initialized the array with a pointer to the copy).

Ari