1

I'm trying to implement a menu for my Shape program. I've implemented all of the shapes classes. Two are deriving straight from the abstract class "Shape", and two others are deriving from a class called "Polygon" which derives from "Shape" as shown bellow:

Shape -> Polygon -> Rectangle, Triangle
  `-> Circle, Arrow

In my menu class, I want to create some sort of an array that can contain the pointers to the objects and with the type of the base class "Shape". But I'm not sure how to do it properly and in a way which will work for all of my shapes, because 2 of my classes aren't deriving from "Shape" directly.

This is my menu class:

class Menu
{
protected:
    //array of derived objects 
public:

    Menu();
    ~Menu();

    // more functions..
    void addShape(Shape& shape);
    void deleteAllShapes();
    void deleteShape(Shape& shape);
    void printDetails(Shape& shape);
private:
    Canvas _canvas; //Ignore, I use this program to eventually draw this objects to a cool GUI
};

And in the function "addShape(Shape& shape);", Which I want to use to add each given shape to my array. How can I implement the addition of new objects to it? And also, how can I check if the given object is deriving from "Polygon" or not? Because if so then I need to call the member functions differently as far as I understand.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
Kidsm
  • 308
  • 2
  • 14
  • 2
    Whether they derive from `Shape` directly or indirectly does not matter - it works the same way. You shouldn't have to call the member functions differently - all shapes should have the same interface (and do different things by implementing the `virtual` methods of the interface differently). But you shouldn't have to change anything about how you call those member functions depending on the type - that would violate the https://en.wikipedia.org/wiki/Liskov_substitution_principle – Max Langhof Dec 06 '19 at 17:35
  • You should think about who should own the shapes. If `Menu` is the owner, use an array of `std::unique_ptr`. If it does not, in `Menu`, you should either use `Shape*` or `std::shared_ptr` depending on whether you are sure that the lifetime of the shapes will be longer than that of `Menu` or not. – n314159 Dec 06 '19 at 17:38
  • `std::vector>` or `std::vector>` should be your goto storage choices. The one you pick will be based on your ownership requirements. – NathanOliver Dec 06 '19 at 17:38
  • But what if i want to use a function like "printDetails", which supposes to print the details about the given shape, I cant call the print function in the same way for all of the objects because some of them aren't deriving directly – Kidsm Dec 06 '19 at 17:40
  • @NathanOliver-ReinstateMonica What's is the different between the two? My menu should work until the end of the main program that I'll implement afterwards – Kidsm Dec 06 '19 at 17:40
  • A `std::unique_ptr` has unique ownership of the pointer it holds, meaning it is not copyable. A `std::shared_ptr` has shared ownership and can be copied. If multiple places depend on the pointer being alive then you want a `shared_ptr` If only one object depends on it, and just passes references down the call stack, then you want a `unique_ptr`. 80-90% of the time `unique_ptr` is the correct choice. – NathanOliver Dec 06 '19 at 17:43
  • Public inheritance models a "is-a" relationship which is transitive, ie every Polygon is a Shape, every Rectangle is a Polygon, ergo every Rectangle is a Shape. It is rare that such analogies dont break on the smallest drag, but here the analogy holds – 463035818_is_not_an_ai Dec 06 '19 at 17:48
  • @Kidsm "deriving directly" should not matter. All children will have the same `virtual` functions, either because a more abstract class in the hierarchy implements the function in the same way the derived class would or because the derived class implements its own version that does exactly what is required for that class. – user4581301 Dec 06 '19 at 17:48
  • What you really have to watch out for is when the hierarchy LOOKS sensible, but isn't in practice. @MaxLanghof already brought up the Liskov Substitution Principle, [here is an example of the Principle in action](https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle) focusing on rectangles and their relationship to squares. – user4581301 Dec 06 '19 at 17:52
  • Unrelated: Here's a neat way to make a simple menu even simpler: [Simple text menu in C++](https://stackoverflow.com/questions/290484/simple-text-menu-in-c). That answer is a bit old. `boost::function` has since been absorbed into the C++ Standard Library as `std::function` – user4581301 Dec 06 '19 at 17:55
  • `addShape(Shape& shape);` This is **NOT** a good idea. Use (smart) pointers. Reserve reference parameters for things your function does *not* keep. – n. m. could be an AI Dec 06 '19 at 19:07
  • Also `void printDetails(Shape& shape);` This doesn't make much sense. If you have a shape in hand and you want to print its details, you usually do not need any extra stuff like a menu. `deleteShape` is also rather suspect. If you hold all the shapes anyway, why do you need to store them in a menu? – n. m. could be an AI Dec 06 '19 at 19:10

1 Answers1

1

I see that you have an array in Menu, let's say:

Shape* myshapes[10];

The shapes can be Rectangles, Triangles, Circles etc. What you want is to be able to use the Menu's printDetails() method like this:

    void printDetails()
    {
        for(int i = 0; i < size; i++)
        {
            cout << "Index " << i << " has " << myshapes[i]->getShapeName() << endl;
        }
    }

The getShapeName() will return a string, e.g. "Rectangle" if it is Rectangle. You will be able to do this with the help of pure virtual function. The pure virtual function must be in the abstract class Shape, which has:

virtual string getShapeName() = 0; //pure virtual

It means that we are expecting a definition for this function in the derived class. This way you will be able to use getShapeName() method using the Shape pointers in the shapes array, which will tell you whether the shape is Rectangle, Triangle, or Circle etc.

class Shape
{
    public:
    virtual string getShapeName() = 0;
};

class Circle : public Shape
{
    private:
    int radius;

    public:
    Circle(int r) { radius = r; cout << "Circle created!\n"; }
    string getShapeName() { return "Circle"; }
};

class Arrow : public Shape
{
    private:
    int length;

    public:
    Arrow(int l) { length = l; cout << "Arrow created!\n"; }
    string getShapeName() { return "Arrow"; }
};

class Polygon : public Shape
{
    public:
    virtual string getShapeName() = 0;
};

class Triangle : public Polygon
{
    private:
    int x, y, z;

    public:
    Triangle(int a, int b, int c) { x = a; y = b; z = c; cout << "Triangle created!\n"; }
    string getShapeName() { return "Triangle"; }
};

class Rectangle : public Polygon
{
    private:
    int length;
    int width;

    public:
    Rectangle(int l, int w){ length = l; width = w; cout << "Rectangle created!\n"; }
    string getShapeName() { return "Rectangle"; }
};

To implement the addShape() method you can do this:

void addShape(Shape &shape)
{
    myshapes[count] = &shape;
    count++;
}

Also, keep in mind to pass the Shape by reference or by using pointer, in the addShape() method. I hope this helps... Best of luck :-)

Ali Sajjad
  • 3,589
  • 1
  • 28
  • 38
  • A note on `addShape`: take care that the provided `Shape` has a long enough lifetime to outlive all uses of it. A function like this is prone to misuse by folks passing in a local variable. – user4581301 Dec 06 '19 at 20:03
  • Wow! thank you for the informative comment. I would also like to know what is the difference between passing the Shape by reference or by pointer :o – Kidsm Dec 06 '19 at 20:56
  • Thanks! You're totally right about this, I should have mentioned it. The problem arises when you call addShape() from a function which ends too soon, so the Shape no longer exists, and the array of shape pointers has an entry which points to some garbage. Could you please also mention the best workaround for this? Hi @Kidsm ! Passing by value and by reference has the same effect. The difference is that when you pass by pointer, the variable is treated as a Shape* and when you pass by reference, the variable is treated as Shape, to get the address you write &shape. – Ali Sajjad Dec 07 '19 at 06:57