inheritance & polymorphism

I have an exercise where the following class hierarchy must be adhered to:-
Base class - Shape

then 2 derived classes
2d shape
3d shape

then we have
circle, square, triangle all inherited from 2d shape class
cube, cylinder, cuboid all inherited from 3d shape.

Each TwoDimensionalShape should contain function getArea to calculate the area of the two-dimensional shape. Each ThreeDimensionalShape should have mem- ber functions getArea and getVolume to calculate the surface area and volume, respectively, of the three-dimensional shape. Create a program that uses a vector of Shape pointers to objects of each concrete class in the hierarchy. The program should print the object to which each vector element points. Also, in the loop that processes all the shapes in the vector, determine whether each shape is a TwoDimensionalShape or a ThreeDimensionalShape. If a shape is a TwoDimensionalShape, dis- play its area. If a shape is a ThreeDimensionalShape, display its area and volume.

My issue is I am having trouble working out how to put what where so to speak. Is it right to put length, breadth, height in my base class with virtual functions for area volume and shape type?

So when the program is run it will show the area and/or volume for each shape with the correct type of shape.

Last edited on
The base class should have the methods common among all the sub-classes, so:
1
2
3
4
5
Shape {getArea()const, printObject()const}
2DShape : Shape {/*can't think of any methods or attributes particular to this class that can't
 be put in Shape itself that therefore raises the question if this class is really necessary
 since getArea()const can just as well be put in Shape*/}
3DShape : Shape {getVolume()const}

Is it right to put length, breadth, height in my base class

No, because for one what are you going to call them? It'd be a stretch to have one term encompass the radius of a circle, the side of a square and the length/width of a rectangle. I suppose you could all them dimension1, dimension2 etc then these terms would be meaningless when read from the program and there'd be unused variables as well
Last edited on
Thats exactly what I was having trouble with. Why bother with the 2d shape class?
Is it enough to omit that class and say all shapes are 2d unless they explicitly say otherwise?

Thanks for your help!!
This assignment violates the the Liskov substitution principle, and parts of SOLID.

https://en.wikipedia.org/wiki/Liskov_substitution_principle
https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

A "3D Shape" is not really a shape in the true sense of the word, rather a "3D Model". So one could argue that "3D Model" IS NOT A (as opposed to IS A) "3D Shape". The IS A is one of the important relationships: the others are HAS A, and USES A.

Also, in the loop that processes all the shapes in the vector, determine whether each shape is a TwoDimensionalShape or a ThreeDimensionalShape.


That statement is also a pointer to bad OOP design. One should be able to traverse the vector without having to do any casting or RTTI.

Also, to do polymorphism, one needs to have an interface of pure virtual functions which cover all of the derived classes. The same functions should apply to all the derived classes - this goes back to the IS A concept.

Good Luck !!

Thanks! Can you explain
"That statement is also a pointer to bad OOP design. One should be able to traverse the vector without having to do any casting or RTTI."
in more detail please!
Those are symptoms of bad design. The user interface functions should apply to all the derived classes: one shouldn't have to "shoe horn" to force the code to work. By casting I mean dynamic_cast, and RTTI is Run Time Type Identification

The problem here is the volume function: what happens when a volume function is applied to a circle? It doesn't exist. Having a volume function for the 2D objects that returns 0.0 is a cop out, even though it might be a virtual function defined once. That might be a strategy for a "conforming" assignment though :+) I would ask your Tutor.


Edit:

It's late my end ......... ZZZZZZZZZzzzzzzz
Last edited on
Thanks for your input!! I appreciate it. Unfortunately I don't have a tutor - I am working through C++ How to Program 8 on my own, and have managed to get this far with the help of this excellent forum. I am at polymorphism... lol

I understand what you are saying, maybe I should take a different approach to the question. Have a shape baseclass, then maybe have derived classes onesdided, 3sided, 4sided. then inherit from these circle(onesided), triangle(3sided) square, rectangle(4sided)

What do you think?

James
What will you gain by extra complexity? / Will the added levels reduce complexity?


All shapes are shapes. All shape can show themselves. Some shapes show just area, while others show area and volume. Not quite unlike the Fubars:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Fubar {
    virtual void print() = 0;
    void bah() {}
};

struct Foo : public Fubar {
    void print() { bah(); }
};

struct Bar : public Fubar {
    void print() { bah(); humbug(); }
    void humbug() {}
};

int main()
{
  Foo a;
  Bar b;
  Fubar * pa = &a;
  Fubar * pb = &b;
  pa->print();
  pb->print();
  return 0;
}

The Fubar is not all abstract. All Fubar can bah, so it makes sense to implement it there.
(In your case the bah, or area, would be pure virtual, because you do implement it in the derived classes.)
I reckon there are several areas of confusion in all this.

In AutoCAD parlance, there is the concept of a Solid - that is something which can have a volume. 2d shapes (that is, planar) can exist in a 3d coordinate system. Normally a system is either all 2d or all 3d.

Also in AutoCAD there is a DrawingEntity concept. So this is the base class; derived from it are Shape, Arc, Point, Line, Solid, Text, Dimension etc. Further derived concrete classes come from those.

The base class should only have functions which apply to all derived classes. The existence of a Print or Draw functions justifies the existence of the DrawingEntity class (or in the Shape class as it stands now). The DrawingEntity class also has Layer, Colour etc, because all the Entities have these.

Even the Area function is problematic in the Shape class (as it stands now, like having Area in the DrawingEntity class), because even if it were applied to 2D and 3d objects (as we could using a Shape pointer), it's not quite the same concept: for example it is probably wrong to sum all the areas of 2D and 3d objects. So we could have a pure virtual function Area() in the Shape class (in my version of the inheritance tree) ; and the Solid class would have SurfaceArea() and Volume() .

Another problem with Area() is we now have various Arc classes, so that is another violation: we added something new, and now we need to change the base class.

One of the things about class design is the concept of pushing variables and functions as high up the tree as possible. Sometimes it makes sense to create a new abstract class to get the most out of that idea. For example in a Role Playing Game, we have Player and Enemy; they both have a Health value. So we create the base class Actor which has the health value, and Player and Enemy derive from it. I did the same thing with DrawingEntity.

Another important thing is being careful naming things. IMO having 2D shape and 3D Shape leads to bad design; I made the distinction between Shape and Solid. The other distinction was Area (2d) and SurfaceArea (3d)

The other thing is that there are a lot of "Bad" books out there. Consider getting one with with a good reputation.

http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list#388282
then maybe have derived classes onesdided, 3sided, 4sided. then inherit from these circle(onesided), triangle(3sided) square, rectangle(4sided)


Once you learn about templates, you could have the type RegularPolygon<N, Radius> where N refers to the number of sides. I wouldn't use it for circles though.
Last edited on
Hmmm sounds like it is particularly difficult. My base class should have functions that apply to all derivatives.
Clearly this is difficult.
Circle - perimter PI*D, area PI r^2
Triangle - perimeter side1+side2+side3, area 1/2 side1*side2
Square - perimeter 2*lenght + 2*breadth, area lenght * breadth

And that's just 3! So from above there isnt much in common to put in my base class to inherit?
Last edited on
The RegularPolygon<N, Radius> constructor uses the same code no matter how many sides there are, up to a limit: say 3 to 1024.

A regular polygon has points which all lie on a circle. The polygon can be divided into N isosceles triangular segments with the angle 360/N at the centre, so with some trigonometry one can work out the co-ordinates of each point and put them in a vector. The area and perimeter are easy to understand if you work from the same segment idea.

Note that one doesn't use this for a circle.

The common things to put in the Shape class are pure virtual Area() and Perimeter() functions. RegularPolygon is a concrete class, which has the actual implementation of those functions.
closed account (48T7M4Gy)
Irregular Shapes or whatever you want to call them aren't quite so easy but this is a quick way of looking at inheritance. (E&OE)

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
#include <iostream>

// GENERAL (REGULAR) SHAPE
class Shape
{
protected:
    double length;
    double area;
    
public:
    double getLength(){ return length; }
    double getArea(){ return area; }
};

// 2D SHAPE
class Shape_2D:public Shape
{
protected:
    double perimeter;
    
public:
    double getPerimeter(){ return perimeter;}
};

class Circle: public Shape_2D
{
public:
    Circle( double aRadius = 0)
    {
        length = aRadius;
        area = 3.14 * length * length;
        perimeter = 3.14 * 2 * length;
    }
};

class Square: public Shape_2D
{
public:
    Square( double aSize = 0)
    {
        length = aSize;
        area = length * length;
        perimeter = 4 * length;
    }
};

class Triangle: public Shape_2D
{
public:
    Triangle( double aSize = 0)
    {
        length = aSize;
        area = 0.433 * length * length;
        perimeter = 3 * length;
    }
};

// 3D SHAPE
class Shape_3D:public Shape
{
protected:
    double volume;
    
public:
    double getVolume(){ return volume; };
};

class Cube:public Shape_3D
{
public:
    Cube( double aSize = 0)
    {
        length = aSize;
        area = 6 * length * length;
        volume = length * length * length;
    }
};

class Sphere:public Shape_3D
{
public:
    Sphere( double aRadius = 0)
    {
        length = aRadius;
        area = 4 * 3.14 * length * length;
        volume = 4.0/3 * 3.14 * length * length * length;
    }
};

int main()
{
    Circle circ(2);
    std::cout << "  Circle: " << circ.getLength() << ' ' << circ.getPerimeter() <<  ' ' << circ.getArea() << '\n';
    
    Square squa(2);
    std::cout << "  Square: " << squa.getLength() << ' ' << squa.getPerimeter() <<  ' ' << squa.getArea() << '\n';
    
    Triangle tria(2);
    std::cout << "Triangle: " << tria.getLength() << ' ' << tria.getPerimeter() <<  ' ' << tria.getArea() << '\n';
    
    Cube cube(2);
    std::cout << "    Cube: " << cube.getLength() << ' ' << cube.getArea() << ' ' << cube.getVolume() << '\n';
    
    Sphere sphr(2);
    std::cout << "  Sphere: " << sphr.getLength() << ' ' << sphr.getArea() << ' ' << sphr.getVolume() << '\n';
    
    return 0;
}


Circle: 2 12.56 12.56
  Square: 2 8 4
Triangle: 2 6 1.732
    Cube: 2 24 8
  Sphere: 2 50.24 33.4933
Program ended with exit code: 0
Thanks everyone. I ned to go and think the best way to implement it. Ideally I want to create a vector of pointers to bas class and implement it that way...

Thanks again

James
jamesfarrow wrote:
I am working through C++ How to Program 8 on my own

Just because it's in a book, doesn't mean it's correct.
I can sympathize with the Dietels: even if they actually know what they are doing (wouldn't bet on it), it is difficult to come up with simple correct examples of dynamic polymorphism. They probably want to demonstrate some syntax basics and needed a strawman class hierarchy. If you ignore that it makes no sense, you can still learn how to define and call a virtual function or how to identify a polymorphic type at runtime.


TheIdeasMan wrote:
http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list#388282

This is the best answer.
I reckon it's quite constructive to take bad examples from books, and have the members here tear them to shreds, and specify how they would do it instead. As long as the question is posed that way, not trying insist that the answer is sledge hammered until it fits the question.
closed account (48T7M4Gy)
It's possibly flogging a dead horse but a few major problems I had with this appear to have fairly orthodox and well known solutions if you go into it. Stroustrup tends to make a meal of it in his book spreading the 'sweat' over a couple of chapters and very tersely ( I bet no surprise from Stroustrup on that score). I think if there was any force-fitting it's in the C++ language itself.

Slicing, override, virtual, inherited operator overloading, relevant STL use etc can all be learned from this one simple problem. FWIW regular objects are simply a generalization of what is going on here. gunner alluded to it and it's not so difficult to cater for cuboids and rectangles along with circles all with different number of dimensions. The hierarchy of inheritance is not fixed because the 3d shape can be inherited from the 2d shape just as much as each can be separately inherited from the base shape. That's just a design choice.

Where the criticisms fail, especially the 'attack' on Deitel, is the issues are not really about the nature of the objects being modelled and class names or whether they are strawmen or whether Autocad does this or that. The criticisms go nowhere in terms of addressing the underlying programming issues and wonders of C++ and how that language handles OOP inheritance and polymorphism problems. I haven't read what Deitel says but I think he and his son have been around long enough to know a bit about teaching C++ and the difficulties students have of mastering some of the concepts in C++ which after all is only a programming language. Their books are OK.

Maybe the only reason C++ standards hasn't ironed out some of these things is because the language rapidly collapses into Java. :)

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include <iostream>
#include <string>
#include <vector>

#include <iomanip>

// GENERAL (REGULAR) SHAPE
class Shape
{
protected: // ACCESSIBLE ONLY BY SUB-CLASSES
    std::string name = "?";
    double length = 0;
    double area = 0;
    
public:
    Shape(){};
    ~Shape(){};
    
    virtual std::string getName()const{ return name; }
    virtual double getLength() const { return length; }
    virtual double getArea()const{ return area; }
    
    friend std::ostream& operator<<(std::ostream &out, const Shape &aShape)
    {
        return aShape.print(out);
    }
    
    virtual std::ostream& print(std::ostream& out) const
    {
        out << "Shape - default information";
        return out;
    }
};

// 2D GENERAL (REGULAR) SHAPE
class Shape_2D:public Shape
{
protected:
    double perimeter = 0;
    double getPerimeter() const { return perimeter;}
public:
    Shape_2D(){};
    ~Shape_2D(){};
    
    virtual std::ostream& print(std::ostream& out) const override
    {
        out
        << getName() << std::left
        << " Size: " << std::setw(4) << getLength()
        << " Perimeter: " << std::setw(8) << getPerimeter()
        << " Area: " << std::setw(8) << getArea();
        return out;
        }
        };
        
        class Circle:public Shape_2D
        {
        public:
            Circle( double aRadius = 0)
            {
                name = "  Circle (2D)";
                length = aRadius;
                area = 3.14 * length * length;
                perimeter = 3.14 * 2 * length;
            }
            ~Circle(){};
        };
        
        class Square: public Shape_2D
        {
        public:
            Square( double aSize = 0)
            {
                name = "  Square (2D)";
                length = aSize;
                area = length * length;
                perimeter = 4 * length;
            }
            ~Square(){};
        };
        
        class Triangle: public Shape_2D
        {
        public:
            Triangle( double aSize = 0)
            {
                name = "Triangle (2D)";
                length = aSize;
                area = 0.433 * length * length;
                perimeter = 3 * length;
            }
            ~Triangle(){};
        };
        
        // 3D GENERAL (REGULAR) SHAPE
        class Shape_3D:public Shape
        {
        protected:
            double volume = 0;
            double getVolume() const { return volume; };
            
        public:
            Shape_3D(){};
            ~Shape_3D(){};
            
            virtual std::ostream& print(std::ostream& out) const override
            {
                out
                << getName() << std::right
                << " Size: " << std::setw(4) << getLength()
                << " Area: " << std::setw(8) << getArea()
                << " Volume: " << std::setw(8) << getVolume();
                return out;
            }
        };
        
        class Cube:public Shape_3D
        {
        public:
            Cube( double aSize = 0)
            {
                name = "    Cube (3D)";
                length = aSize;
                area = 6 * length * length;
                volume = length * length * length;
            }
            ~Cube(){};
        };
        
        class Sphere:public Shape_3D
        {
        public:
            Sphere( double aRadius = 0)
            {
                name = "  Sphere (3D)";
                length = aRadius;
                area = 4 * 3.14 * length * length;
                volume = 4/3 * 3.14 * length * length * length;
            }
            ~Sphere(){};
        };
        
        int main()
        {
            std::cout << "MAKE SOME SHAPES\n";
            Circle circ(2.1);
            std::cout << circ << '\n';
            
            Square squa(3);
            std::cout << squa << '\n';
            
            Triangle tria(4);
            std::cout << tria << '\n';
            
            Cube cube(5);
            std::cout << cube << '\n';
            
            Sphere sphr(6.35);
            std::cout << sphr << '\n';
            
            std::cout << "\nSTORE POINTERS IN A VECTOR\n";
            std::vector<Shape* > vect;
            
            vect.push_back(&circ);
            vect.push_back(&squa);
            vect.push_back(&tria);
            vect.push_back(&cube);
            vect.push_back(&sphr);
            
            std::cout << "\nRETRIEVE SHAPE INFO FROM VECTOR\n";
            for( auto i: vect)
                std::cout << *i << '\n';
            
            return 0;
        }


MAKE SOME SHAPES
  Circle (2D) Size: 2.1  Perimeter: 13.188   Area: 13.8474 
  Square (2D) Size: 3    Perimeter: 12       Area: 9       
Triangle (2D) Size: 4    Perimeter: 12       Area: 6.928   
    Cube (3D) Size:    5 Area:      150 Volume:      125
  Sphere (3D) Size: 6.35 Area:  506.451 Volume:   803.99

STORE POINTERS IN A VECTOR

RETRIEVE SHAPE INFO FROM VECTOR
  Circle (2D) Size: 2.1  Perimeter: 13.188   Area: 13.8474 
  Square (2D) Size: 3    Perimeter: 12       Area: 9       
Triangle (2D) Size: 4    Perimeter: 12       Area: 6.928   
    Cube (3D) Size:    5 Area:      150 Volume:      125
  Sphere (3D) Size: 6.35 Area:  506.451 Volume:   803.99
Program ended with exit code: 0
Last edited on
Topic archived. No new replies allowed.