[Dynamic Polymorphism/object slicing] Overriden functions in derived classes not called

This code simulates a random dice roll. A Dice object stores an arbitrary number of DieFace (a generic face), including two special faces: MonsterFace (whose getter value is negative) and StarFace (whose getter value is positive).

StarFace and MonsterFace store the values they're constructed with (i.e., StarFace.value may be set negative but the value returned by Value() must be positive; similarly for MonsterFace).

Calling Dice::Roll() returns a DieFace* which can be used to get the face's value.
Callers use DieFace::Value() method to get and set a face's value.

I'm trying to override DieFace::int Value(int). While debugging, I noticed when Roll() returns a MonsterFace or StarFace, the DieFace::Value() gets called and not MonsterFace::Value() nor StarFace::Value().

For example, when Roll() returns the MonsterFace(4000) object, I expect MonsterFace::Value() to return -4000. But instead, I get 4000. StarFace(-3000) should return 3000.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <iostream>
#include <random>
#include <iterator>
#include <vector>
#include <limits>

using std::vector;
using std::cout;
using std::cin;

#define EmptyInteger std::numeric_limits<int>::min()

// [1] https://stackoverflow.com/a/16421677/5972766
template<typename Iter>
Iter select_randomly(Iter start, Iter end) {
    // Seed the generator
    static std::random_device rd;
    static std::mt19937 gen(rd());

    std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
    std::advance(start, dis(gen));
    return start;
}

struct DieFace
{
protected:
    int value = 0;

public:
    DieFace() {}
    DieFace(int value) : value(value) {}
    virtual ~DieFace() {}

    // Default getter/setter
    virtual int Value(int newVal = EmptyInteger)
    { 
        return this->value; 
    } 
};

struct MonsterFace : public DieFace
{
    MonsterFace() {} 
    MonsterFace(int monsterValue) : DieFace(monsterValue)
    { 
    }

    // Getter/setter
    int Value(int newVal = EmptyInteger) override
    {
        // What if you want to set DieFace::value to EmptyInteger?
        if (newVal == EmptyInteger) 
        {   // User wants to get the monsterValue
            // monsterValues are negative
            if (DieFace::Value() > 0) return (-1 * DieFace::Value());
        }
        else
        {   // User wants to set the value
            DieFace::value = newVal < 0 ? newVal : newVal * -1;
        }

        return DieFace::Value();
    }
};


struct StarFace : public DieFace
{
    StarFace() {}
    StarFace(int starValue) : DieFace(starValue)
    { 
    }

    // Getter/setter
    int Value(int newVal = EmptyInteger) override
    {
        // What if you want to set DieFace::value to EmptyInteger?
        if (newVal == EmptyInteger)
        {   // User wants to get the starValue
            // starValues are positive
            if (DieFace::Value() < 0) return (-1 * DieFace::Value());
        }
        else
        {   // User wants to set the value
            DieFace::value = newVal > 0 ? newVal : newVal * -1;
        }

        return DieFace::Value();
    }
};

struct Dice
{
    vector<DieFace> Faces;
    
    Dice() {}
    Dice(vector<DieFace>& faces) : Faces(faces) {}

    DieFace* Roll()
    {
        if (Faces.empty()) return nullptr;

        // Return a random DieFace from Faces or nullptr if this is a Faceless die
        return &(*select_randomly(Faces.begin(), Faces.end()));
    }
};

int main()
{
    // Make some faces
    auto faces = vector<DieFace>
    { 
        MonsterFace(-2000), MonsterFace(4000), 
        StarFace(1000), StarFace(-3000),
        DieFace(1), DieFace(2), DieFace(3), DieFace(4), DieFace(5), DieFace(6) 
    };

    // Make a dice with those faces
    Dice* dice = new Dice(faces);

    // Type any valid integer to roll the die
    int userVal = 0; 
    while (cin >> userVal)
    {
        cout << "Rolled:" << dice->Roll()->Value() << "\n";
    }
    
    delete dice;

    cout << "Press Enter to exit ...\n";
    int pauseConsole;
    cin >> pauseConsole;
}


I think it has to do with the vector used to initialize dice:

1
2
3
4
5
6
auto faces = vector<DieFace>
{ 
   MonsterFace(-2000), MonsterFace(4000), 
   StarFace(1000), StarFace(-3000),
   DieFace(1), DieFace(2), DieFace(3), DieFace(4), DieFace(5), DieFace(6) 
};


Maybe they should be vector<DieFace*> ... but why?
Last edited on
You are correct. You should store pointers in the vector.

A std::vector<DieFace> can only store objects whose type is exactly DieFace. It cannot store objects of types that are derived from DieFace.

What you're doing when you initialize the vector is called slicing. Look it up.
https://en.wikipedia.org/wiki/Object_slicing
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual
Last edited on
Peter87 wrote:
What you're doing when you initialize the vector is called slicing.

I read a bit about it but it looks like it can happen in many situations.

1. When a method parameter takes a pointer-to-base-type and it's passed a pointer-to-derived-type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// [1]
struct Foo { ... };
struct Fooz : Foo { ... };

struct BigBox 
{
   bool getFoo(Foo *fillMe) { ... }
}

BigBox theBigBox; 
int main() 
{ 
   Fooz fz;
   theBigBox.getFoo(&fz); // mbozzi: slices fz since BigBox::getFoo takes Foo*
}


2. When a base type is assigned a derived type.
1
2
3
4
5
6
// [2] "The benign case"
struct A { int w=0, x=1; };
struct B : public A { int y=2, z=3; };

B b; 
A a = b; // Slicing b.y, b.z 


3. When a reference to a base type is initialized with a derived type
1
2
3
4
5
6
// [2] "The treacherous case"
B b1;
B b2;
b2.w = -2; b2.x = -1; b2.y = 4; b2.z = 5; // Change b2 a bit
A& a_ref = b2; 
a_ref = b1; // Slicing occurs during copy assignment; only b1's A:: members are copied. C++ doesn't treat assignment operator as virtual by default. 


And now, it can happen when initializing a vector (and I'm guessing any other containers). Is that because the initializer list:

1
2
3
4
5
6
auto faces = vector<DieFace>
{ 
   MonsterFace(-2000), MonsterFace(4000), 
   StarFace(1000), StarFace(-3000),
   DieFace(1), DieFace(2), DieFace(3), DieFace(4), DieFace(5), DieFace(6) 
};

performs a copy assignment of those derived objects to DieFace (base) objects on the heap?

Maybe my error was assuming that heap-allocation somehow prevented slicing. The fact that those DieFace objects are heap-allocated won't prevent slicing, right?

In a way, I can see that it vaguely resembles case 2, "The benign case", except where A is stack-allocated, storage for those derived objects just happen to be heap-allocated.

... yup. Visual Studio also says this is slicing:

1
2
A *a = new A(); // Storage for a is heap-allocated
*a = b1; // Still slicing. 


[1] Storing Items In an Array of Custom Lists
https://cplusplus.com/forum/beginner/284285/
[2] SO: What is object slicing?
https://stackoverflow.com/a/274634/5972766
Last edited on
 
theBigBox.getFoo(&fz); // mbozzi: slices fz since BigBox::getFoo takes Foo* 

Accessing a derived class through a pointer-to-base isn't a problem. The problem in that case was inside BigBox::getFoo, which overwrote the base class subobject of the pointed-to Fooz with a different Foo, similar to case 3 above.

Typically, derived classes are larger than their base classes. This is because a derived class object contains all of its base classes.
Last edited on
mbozzi wrote:
The problem in that case was inside BigBox::getFoo, which overwrote the base class subobject of the pointed-to Fooz with a different Foo, similar to case 3 above.

Oh, I see.

1
2
3
4
5
6
7
8
9
10
11
struct BigBox 
{
   bool getFoo(Foo* fillMe) { *fillMe = items[idx++]; return true; }
   ...
private:
   static vector<Foo> items; // Foo objects from the heap
};

...
Fooz fz;
theBigBox.getFoo(&fz); // Passes a Fooz 


*fillMe has dynamic type Fooz and was assigned Foo objects from the heap. Same mistake made here: any derived object copied-to/constructed-in the vector item would have been sliced.
Last edited on
Topic archived. No new replies allowed.