2

I have a base class called Effect that is defined like this:

class Effect
{
public:
    virtual void apply(int a, int b);
};

void Effect::apply(int a, int b)
{
}

And some subclasses of Effect, all defined like this:

#pragma once

#include "Effect.h" 

class SomeSubclassOfEffect: public Effect
{
public:
    void apply(int a, int b);
};

void SomeSubclassOfEffect::apply(int a, int b)
{ 
    //Magic
}

Now, in some other part of my application I have this:

Effect effects[6]...

effects[0] = SomeSubclassOfEffect();

What I'm trying to do is to call the corresponding overridden version of apply(int a, int b) by means of effects[whatever].apply(x, y), but I'm getting the parent class' one instead. Why am I getting this result?

Laura
  • 279
  • 1
  • 12
  • 1
    You shouldn't be getting the results you describe. Are you using C++11 or later? If so, the `apply` functions you override should be declared `override`. – Drew Dormann Jan 21 '15 at 19:13
  • Read a bit on [Polymorphism.](http://www.cplusplus.com/doc/tutorial/polymorphism/) it explains everything you need. – PlamZ Jan 21 '15 at 19:13
  • 3
    You need to be using pointers – NathanOliver Jan 21 '15 at 19:14
  • @DrewDormann I'm using C++98 (working with Visual C++ 2010) – Laura Jan 21 '15 at 19:14
  • @NathanOliver So should I store pointers to child instances inside the array? – Laura Jan 21 '15 at 19:18
  • You should use pointers here! By declaring `Effect effects[6]` every object stored in this array is of class Effect and if you store an object of a derived class it is sliced to the base class because memory is statically only reserved for objects of class Effect. To enable polymorphism in this case you have to switch to an array of pointers. – Michbeckable Jan 21 '15 at 19:25
  • 2
    @Laura You would use `Effect* effects[6]; effects[0] = new SomeSubclassOfEffect();` – NathanOliver Jan 21 '15 at 19:31

2 Answers2

7

C++ uses value semantics by default, not reference semantics, so effects[0] = SomeSubclassOfEffect(); slices off all the child-specific information, leaving only the parent information.

Mark B
  • 95,107
  • 10
  • 109
  • 188
4

The way c++ places classes in memory is to put the base class first then derived class information after that.

|base class|derived class|
^          ^             ^
a          b             c

If you took the address of one of these objects that would be memory address a, the end of the class is at address c in this diagram. The information in your derived class is in the memory segment from b to c. There's 2 versions of apply stored in the memory here: a version for the base class and one for the derived class which one gets called is based on the type of the object. When you create an array with Effect effects[3] for example you get memory that looks like this:

|effect|effect|effect|

C++ just allocates you just enough space to fit an effect class per array index and no more. Now the problem is that you try to place and object that looks like this into the first element of the array:

|effect|somesubclass|

This can't fit into one array element so c++ slices off the end of the class, which includes the information about the subclass to put it in the array which loses information. Any members of the derived class are now gone! The array is of type effect so anything placed in the array will be treated as though it were that type and methods will be called accordingly. This then causes problems like the one you encountered.

This is known as the slicing problem: What is object slicing?

To deal with this you should never use a polymorphic array of objects, instead use pointers to the objects because the pointers are all the same size and hence will properly work when used inside the array. A pointer to the base class will be the same size as a pointer to the derived class, it's just the pointer to the start of the memory block after all. The pointers will now point to the correct type and hence the correct versions of functions will be called.

Community
  • 1
  • 1
shuttle87
  • 15,466
  • 11
  • 77
  • 106
  • Actually, the *size* isn't the crux of the matter, though if the derived class has extra-members and thus is bigger, that makes the mis-fit obvious. The problem is that the array has type "array of base", and any assignments and function-calls will treat it as such. – Deduplicator Jan 21 '15 at 19:31
  • @Deduplicator, I realized this just after I posted, hopefully the latest edit clarifies that point. – shuttle87 Jan 21 '15 at 19:38