4
\$\begingroup\$

I'm reading through the chapter on inheritance from C++ Primer 5th Edition. It asks

Organize the following type into an inheritance hierarchy:

(b) Geometric primitives (such as box, circle, sphere, cone)

Identify some of the likely virtual functions as well as public and protected members.

I was hoping this is a suitable arrangement for the organisation of the hierarchy. My line of thinking was:

  1. The most general property of any shape is its dimension. Thus the code could be factored in to having a base class at the top named Shape which would contain this piece of data.

  2. The necessary member functions will change depending on whether I have a 1D, 2D or 3D shape. 2D shapes will require an area and perimeter function, and 3D shapes a surfaceArea and volume function. The operations of these functions will vary depending on the shape. So define abstract base classes corresponding to each dimension with pure-virtual member functions.

  3. Derive classes from the dimension classes corresponding to the final geometric shapes.

My main concern is that maybe the Shape class isn't all that necessary if its only purpose is to hold dimension and the static member pi. A second concern is how to prevent creation of Shape objects, since it only represents a concept and nothing tangible. I'm guessing to do this I would need to define the destructor as pure virtual - although I have not reached this stage of reading.

Shape.h:

#ifndef SHAPE
#define SHAPE
#include <cmath> //sqrt needed
class Shape{
public:
 Shape() = default;
 Shape(unsigned d): dimensions(d) { }
 virtual ~Shape() = default;
private:
 unsigned dimensions = 0;
protected:
 static const double pi;
};
class DimensionOne : public Shape {
public:
 DimensionOne(): Shape(1) { }
 virtual double length() const = 0;
};
class DimensionTwo : public Shape {
public:
 DimensionTwo(): Shape(2) { }
 virtual double perimeter() const = 0;
 virtual double area() const = 0;
};
class DimensionThree : public Shape {
public:
 DimensionThree(): Shape(3) { }
 virtual double surfaceArea() const = 0;
 virtual double volume() const = 0;
};
//2D SHAPES
class Rectangle : public DimensionTwo {
public:
 Rectangle() = default;
 Rectangle(double a): DimensionTwo(), length(a), width(a) { }
 Rectangle(double x, double y): DimensionTwo(), length(x), width(y) { }
 double perimeter() const override { return 2*(length+width); }
 double area() const override { return length*width;}
private:
 double length = 0, width = 0;
};
class Circle : public DimensionTwo {
public:
 Circle() = default;
 Circle(double r): DimensionTwo(), radius(r) { }
 double perimeter() const override { return 2*pi*radius; }
 double area() const override { return pi*radius*radius;}
private:
 double radius = 0;
};
//3D SHAPES
class Box : public DimensionThree {
public:
 Box() = default;
 Box(double a): DimensionThree(), length(a), width(a), height(a) { }
 Box(double x, double y, double z): DimensionThree(), length(x), width(y), height(z) { }
 double surfaceArea() const override { return (2*length*height) + (4*width*height); }
 double volume() const override { return length*width*height;}
private:
 double length = 0, width = 0, height = 0;
};
class Sphere : public DimensionThree {
public:
 Sphere() = default;
 Sphere(double r): DimensionThree(), radius(r) { }
 double surfaceArea() const override { return (4*pi*radius*radius); }
 double volume() const override { return (4*pi*radius*radius*radius)/3;}
private:
 double radius = 0;
};
class Cone: public DimensionThree {
public:
 Cone() = default;
 Cone(double b, double h): DimensionThree(), base(b), height(h) { }
 double surfaceArea() const override { return pi*base*(base + sqrt(base*base + height*height)); }
 double volume() const override { return (pi*base*base*height)/3;}
private:
 double base = 0, height = 0;
};
#endif // SHAPE

Shape.cpp:

#include "Shape.h"
const double Shape::pi = 3.14159;
asked Oct 11, 2015 at 18:45
\$\endgroup\$
2
  • \$\begingroup\$ Well done. But one of the reasons for having a common base class is that is the interface for common features. Why not add a draw() method to Shape so that it has at least one common interface. \$\endgroup\$ Commented Oct 11, 2015 at 19:11
  • \$\begingroup\$ Also you have described the shapes well. But you have not described them in terms of position. So it is hard for them to interact (maybe thats another interface they need to inherit from). But if they had a location you could do intersections unions etc. \$\endgroup\$ Commented Oct 11, 2015 at 19:14

4 Answers 4

1
\$\begingroup\$

A second concern is how to prevent creation of Shape objects, since it only represents a concept and nothing tangible. I'm guessing to do this I would need to define the destructor as pure virtual - although I have not reached this stage of reading.

Yep:

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

Now you can't create objects of Shape.

answered Oct 11, 2015 at 19:16
\$\endgroup\$
1
\$\begingroup\$

It may be easier to read and maintain this by:

  1. Adding a space between each operator instead of cramming them against the operands
  2. Having the class definitions in separate .cpp files

Also, if you're going to have such a small Shapes.cpp file, you might as well just provide the value of pi within the class declaration. I don't think the base class would need further implementation to warrant a separate .cpp file.

answered Oct 11, 2015 at 19:36
\$\endgroup\$
2
  • \$\begingroup\$ Would that not cause issues of multiple definitions of pi if I included Shape.h in multiple .cpp files? \$\endgroup\$ Commented Oct 11, 2015 at 19:46
  • \$\begingroup\$ @AntiElephant: No, because it would only be defined once in the header and nowhere else. \$\endgroup\$ Commented Oct 11, 2015 at 19:47
0
\$\begingroup\$

1.The most general property of any shape is its dimension. Thus the code could be factored in to having a base class at the top named Shape which would contain this piece of data.

Well, as every shape has its dimensionality fixed in the class definition, that info should not be a data member, but provided by a member function:

struct ShapeBase {
 virtual ~Shape() = default;
 virtual unsigned get_dimensions() const = 0; // pure virtual, must be overridden
};

That also takes care of your second concern. And the destructor is still trivial, which is a plus.

Also, there's no reason for giving the base shapes of each dimension different names. Use templates and specialize explicitly:

template <unsigned dim> struct Shape<dim>;
template<> struct Shape<1> : ShapeBase {
 unsigned get_dimensions() const final { return 1; }
 // marked final to forbid overriding further
};
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
answered Oct 11, 2015 at 19:24
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the reply. I heeded your first suggestion. As for templates, I will be learning about templates in due time so I'll come back to it when I know a bit more. \$\endgroup\$ Commented Oct 11, 2015 at 19:47
0
\$\begingroup\$

Bug

Your formula for the surface area of a Box is wrong:

double surfaceArea() const override { return (2*length*height) + (4*width*height); }

It should be:

double surfaceArea() const override { return 2*(length*height + width*height +
 length*width); }
answered Oct 12, 2015 at 1:11
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.