Easy to Make Mistakes in Class Inheritance

This started out as a task to learn simple class inheritance. To work out the syntax and idiomatic way to use them in C++. I just kept typing things that I thought would work and the compiler would slowly teach me what was wrong.

class rectangle : public shape
{
public:
    rectangle(float half) : shape(half)

Getting the syntax right for a virtual function was tricky because other parts of my code was wrong and so I kept trying to fix the wrong thing. I am getting better at using the compilers error messages, but it is not that clear to a newbie.

The standard advice of “starting with something working and build slowly from there” is my own advice – I did not take my own medicine!

Key Things Learnt

Calling the Parent Constructor

With only the concept of inheritance and no experience or reading it is easy to try to directly call the constructor where you want it. However this is a fixed syntax for C++. It has to be in the definition of the child class.

rectangle(float half) : shape(half) {

The Rule of 3 (5 or 7 etc)

The real reason behind this rule is actually subtle and has some dogma attached as well. Due to the dogma it can be hard to decode the real reasons why this rule came about. Others will be able to explain it better than me, so I will leave finding a good description to the reader. I started with just the minimum code in the classes as that made sense, however this failed in a tricky way. So now I will always just add them in and supply the constructor, copy constructor and destructor for classes even if they are set to default.

Copy Elision

The subtle fail is due to the C compiler using a copy-initialisation and also using the default function when not supplied.

I wanted to be able to do copy = original, so I assumed that meant that I should implement the copy via operator=( ). With this operator overload written it worked for normal cases, but then I used

class list copy = original;

things were very different. C++ will perform an optimisation which is to call the copy-constructor. IE it actually does this without telling you:

class list copy = class list(original);

As I had not provided the code for that it would call the default constructor. Hence the internal data that needed to be copied was not.

I found out the hard way by the program not working and having to use the debugger. This showed the copy was full of fresh initialised data.

Removing a Virtual Function’s implementation

Textbook stuff, but you have to be told the syntax:

virtual float volume() = 0; // Pure virtual.

Use the Default Implementation for a Class

Another one that is very useful and follows the Rule of 3.

virtual ~shape() = default; // Default destructor.

Some Code Examples of the Above

class shape
{
public:
    shape(float half)
    {
        // Standing in for more complex processing.
        _width = 2.0f * half; 
    };
    float memory();
    virtual float volume() = 0; // Pure virtual.
    shape() = default;          // Default constructor.
    virtual ~shape() = default; // Default destructor.
    // Needed to make the destructor virtual as  
    // this base class has a virtual function: volume().
protected:
    float _width = 0.0f; 
    float _padding = 0.0f;
};

float shape::memory()
{
    // At one point the compile said it needed to .
    // see "this->volume". This meant other parts .
    // of the class inheritance were wrong.
    // It can be called correct when done properly.

    // Simulates a padding calculation.
    return (float)((int)volume() * 3 / 16 + 1) * 16.0f;
}

class rectangle : public shape
{
public:
    rectangle(float half) : shape(half)
    { 
        //You cant call the parent constructor anytime 
        //you like you have to have it in the definition.
        // shape(half); This is wrong to have it here.
        _height = half;
        _depth = 4.0f;      // always the same.
    }
    float volume()
    {
        return _width * _height * _depth;
    }
    float _height = 0.0f;
    float _depth = 0.0f; 
};

class circle : public shape
{
public:
    circle(float half) : shape(half)
    {
        _depth = half * 0.5f;
    }
    float volume() {
        return 3.14f * _width * _width * _depth;
    }
private:
    float _height = 0.0f;
    float _depth = 0.0f; 
};

class triangle : public shape
{
public:
    triangle(float half) : shape(half)
    {
        _height = half * 2.0f;
        _depth = half;
    }
    float volume()
    {
        return 3.14f * _width * _width * _depth;
    }
private:
    float _height = 0.0f; 
    float _depth = 0.0f; 
};

int main() {
    shape* p1[3];
    float total = 0.0f;
    p1[0] = new circle(12.0f);
    p1[1] = new triangle(3.0f);
    p1[2] = new rectangle(8.0f);
    for (int i = 0; i < 3; ++i)
    {
        total += p1[i]->memory();
    }
    printf("%f", total);
}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.