1

I want to create a class with a function that adds elements to a list that contains subclasses of an abstract superclass.

I have an abstract class "Shape" with multiple subclasses. In my example, I use the subclass "Circle" but there may be "Rectangle", "Polygon" or any other shape.

The ShapeList class looks (simplified) like this:

ShapeList.h file

#include "Shape.h"

class Node;

class ShapeList
{
public:
ShapeList() : first(0){};

void add( const Shape& s );

private:
Node *first;
};

ShapeList.cpp file

#include "ShapeList.h"

class Node
{
public:
    friend ShapeList;
    Shape *shapes;
    Node *next;
    Node( Node *n, Shape *s) : next(n), shapes(s){};

};

void ShapeList::add( const Shape& s ){

    first = new Node(first, s);
}

Super Class Shape (also simplified)

class Shape
{
    public:
        Shape();
        Shape(double x, double y) : posX(x), posY(y){};

    protected:

        virtual const double area() = 0;

        double posX;
        double posY;
};

Subclass Circle which is one of several subclasses

Circle.h file

#include <math.h>
#include <Shape.h>

class Circle : public Shape
{
    public:
        Circle();
        Circle( double x, double y, double r) : Shape(x, y), radie(r){};

        const double area();

    private:
    double radie;
};

Circle.cpp file

#include "Circle.h"

Circle::Circle()
: Shape(0, 0), radie(0)
{}

const double Circle::area(){
    return radie * radie * M_PI; 
}

Then I want to do, for example, somthing like this:

#include <iostream>
using namespace std;
#include "shapelist.h"
#include "Circle.h"

int main()
{
  ShapeList list;
  list.add( Circle( 5,5, 3)  );

}

So the questions are:

How can I pass a subclass as an argument to a function with a superclass parameter? Is it possible? Create add functions for each shape and Template is not an option.

I have tested and changed a lot in add function but I do not get it working. What's wrong?

Just one note is that I have made changes so many times in the code that I may have destroyed something obvious in the code posted here. Can you point out the mistake?

Lajos Arpad
  • 45,912
  • 26
  • 82
  • 148
GorAhl
  • 61
  • 7

1 Answers1

1

Yes, it is possible to pass a subclass as an argument to a function that takes a superclass parameter but only if you pass by reference or by pointer. If you try to pass by value you experience object slicing.

But that is only part of the story. The next important question is about ownership. Who owns the Shape (and the Node for that matter). Who ensures it is deleted? The easiest way of managing ownership is to use automatic storage and then they will be automatically deleted when they go out of scope. In this case you could consider the possibility that the main function holds a Circle object that goes out of scope at the end of the function. You could pass this Circle as a Shape reference to the ShapeList and then the ShapeList is not responsible for deleting it.

int main() {
    ShapeList list;
    Circle c(5, 5, 3);
    list.add( c );
}

However, looking through your code it looks like you have committed to the ShapeList owning Shape. In which case you have to worry about memory management. I warn you, manual memory management is hard to get right (I may have got it wrong below). If you must take ownership of objects by-pointer I strongly recommend using smart-pointers like std::unique_ptr. But let's assume you really want to do manual memory management.

It would be very unusual to pass ownership using a reference so I would change ShapeList::add to take a pointer. If ShapeList does not need to modify the Shape it could be a pointer-to-const:

void ShapeList::add( const Shape *s ){
    first = new Node(first, s);
}

Then in the main function create the Circle using new:

int main() {
    ShapeList list;
    list.add( new Circle( 10, 10, 4) );
}

Now, ShapeList and Node need to worry about manual memory management so you need to consider the rule of three (five). At the very least you need to properly define the destructor for Node and ShapeList and prevent copy construction/assignment. Also Shape must have a virtual destructor in order to properly delete it.

There were a few other minor changes I had to make in order to compile the code without warnings but I'll leave that to you to find:

constexpr double PI = 3.141592653589793238463;

class Shape
{
    public:
        Shape();
        Shape(double x, double y) : posX(x), posY(y){};
        virtual ~Shape() {}  // Important if a pointer-to-shape is being deleted!

        virtual double area() const = 0;

    protected:
        double posX;
        double posY;
};

class Node
{
    public:
        Node *next;
        const Shape *shape;

        Node( Node *n, const Shape *s) : next(n), shape(s){};
        ~Node() { delete next; delete shape; }

        Node(const Node&) = delete;             // Prevent copying to 
        Node& operator=(const Node&) = delete;  // avoid double-deletion.

};

class ShapeList
{
    public:
        ShapeList() : first(nullptr) {};
        ~ShapeList() { delete first; }

        ShapeList(const ShapeList&) = delete;             // Prevent copying to 
        ShapeList& operator=(const ShapeList&) = delete;  // avoid double-deletion.

        void add( const Shape* s ){ first = new Node(first, s); }
    private:
        Node *first;
};

class Circle : public Shape
{
    public:
        Circle(): Shape(0, 0), radie(0) {}
        Circle( double x, double y, double r) : Shape(x, y), radie(r){};

        double area() const { return radie * radie * PI; }

    private:
        double radie;
};

int main() {
    ShapeList list;
    list.add( new Circle( 10, 10, 4) );
}

Another option, if you want to keep your main function looking like it does is for Shape to have a way of polymorphically copying itself to avoid object slicing. Typically, this is using a virtual clone function. Then ShapeList::add could take by-reference then take a "clone" to own internally.

Of course, in practice you don't need to write your own list class given that there is already std::list in the standard library. Putting this together with unique_ptr is simple.

Community
  • 1
  • 1
Chris Drew
  • 14,004
  • 3
  • 30
  • 51