Description
Overview
In this assignment, you’re going to create a series of classes to represent simple 2D and 3D shapes. While the classes themselves are all simple, you will be using inheritance & the concept of polymorphism to store and use a group of these separate classes. You will also create classes which make use of multiple inheritance, as well as private inheritance.
A note about Pi and math functions
While many languages have math libraries of varying size and complexity built into them, C++ includes little, if any, such functionality. The value of PI, for example, is not officially defined in any C++ standard, although some header files provided by various creators of compilers do contain them.
For a standard in this assignment, you should use the following definition of PI, in one of two different ways:
#define PI 3.14159f // Do a simple find-and-replace of PI everywhere in your code const float PI = 3.14159f; // Create an actual variable with PI as its value.
Also, in this assignment you should use the float data type instead of double to ensure proper results.
Lastly, there exists a header file called <math.h> which you can include to get access to some useful functions such as the pow() function, which lets you raise a value to an exponent and return the result.
Description
The basic idea of inheritance is that you have a base class, and you create new classes which derive from that base class. One common use of inheritance is to reuse data and functions: if a base class defines those things, then the derived class doesn’t have to; it inherits them, so in effect, you get “free” code in that class.
Another use of inheritance is to define a class which serves as what other programming languages would call an interface. In C++, the keyword interface doesn’t exist, but some of the classes you create here will serve the same purpose. An interface defines how a class should look on the outside.
The first three classes shown below will serve as these interfaces; all the other classes will derive, ultimately, from those 3 base classes. In this way, all classes in this assignment are Shapes, even if their type is Triangle, Sphere, Circle, etc. Some types of inheritance model the “Is-a” relationship—a triangle IS A shape. A circle IS A shape as well. The “Is-a” model is heavily used in relational databases.
Other types of inheritance model a “has-a” relationship. A car, for example, has an engine—but it isn’t an engine. This type of relationship can still be created, but the process is a bit different.
Class Description
Let’s look at the first level of these classes: the Shape, Shape2D, and Shape3D:
Because at least one of the functions in this class are pure virtual functions (i.e. virtual functions with no definition), this class is an Abstract Base Class. This class declaration says that all Shapes will be able to scale, as well as display their information. Because of the “= 0” at the end of the function, this class does not have to define these functions. All derived classes, however, MUST define these functions.
The Shape2D is derived from the Shape class (so a 2D shape IS A shape). Like the Shape class, it is also an abstract base class. All Shape2D objects have an Area() & ShowArea() function (refer to above snip). In addition, the comparison operators are overloaded, so that a Shape2D can compare itself to other shapes to test different relations, in terms of the shape’s area. Because the nature of that comparison will be the same, there is no need to define these functions multiple times. Instead, derived classes will inherit these.
Ditto[1] for the Shape3D class, but for volume. The comparison operators will have to be defined in terms of these shapes, as they are different than their 2D cousins—they will compare Volume, which a 2D shape doesn’t have.
2D Shapes
The next classes, the 2D shapes themselves, are going to be based on calculating the area of common shapes. The shapes and their respective area formula are:
Shape | Area Formula |
Square | Area = 𝑠2
s = length of side |
Rectangle | Area = w x h
w = width h = height |
Triangle | Area = 𝑏ℎ
b = length of base h = height |
Circle | Area = π𝑟2r = radius |
Ellipse | Area = πab
a = semi-minor axis b = semi-major axis |
Trapezoid | Area =
a = side b = side h = height |
Sector | 1 Area = 𝑟2𝛳
2 r = radius 𝛳 = angle in radians |
Reference: https://www.mathsisfun.com/area.html (It has an area calculator to test against your code)
Because they inherit from Shape2D, you will have to implement the four functions required by the abstract base classes. In addition, each class should have:
- A default constructor
- A constructor which takes in parameters for each of the main components (a square takes one, trapezoid should have 2, etc.). The order of the parameters is as they are listed in the area formula. For example, the Trapezoid class constructor will take 3 floats — a, then b, then height.
Special Requirement: Sector
The Sector class will have a special requirement regarding handling of degrees/radians. The constructor should take in an angle in degrees and convert it to radians. Purely, this could be done with radias, but many math libraries will do this one of two ways, possibly both. There are 2π radians in a circle, or 1 degree is equal to π/180.
3D Shapes
The 3D shapes will be a little different, partly due to how they will derive from the base classes. They will utilize multiple inheritance, and one of the base classes will be derived using private inheritance.
Shape | Base classes | Volume |
TriangularPyramid | public Shape3D, private Triangle
(It IS A Shape3D, and it HAS A triangle) It has a private height variable |
Volume = 𝐴ℎ
A = area of base h = height |
RectangularPyramid | It is a Shape3D, and it has a rectangle It also has a private height variable | Volume = 𝑙𝑤ℎ
l = length of base w = width of base h = height |
Cylinder | It is a Shape3D, and it has a circle It has a height (private) variable too! | Volume = 𝜋𝑟2ℎ
r = radius h = height |
Sphere | It is a Shape3D, and it has a circle
It only needs the radius (private) of its circle to define its volume |
4 Volume = 𝜋𝑟3
3 r = radius
|
Because they inherit from Shape3D, you will have to implement the four functions required by the abstract base classes. In addition, each class should have:
- A default constructor
- A constructor which takes in a parameter for its main component (if it has one), and then anything its base class would need. For instance, if a class derives from a Triangle then it will have to take in anything that triangle needs and pass it along to the main component to perform the desired calculation.
Abstract Base Class
An Abstract Base Class (ABC) is a class you never want to create an instance of; it doesn’t have enough information to be useful on its own. An ABC could have any number of functions or variables, whether public, protected, or private. You cannot create an instance of an abstract base class.
Perhaps you are creating a program that needs to store data about students and faculty members. They share similar data (name, age, email address, etc.), but have their own unique data as well. You might create 3 classes:
// Abstract class. “Just a Person” isn’t enough data
class Person class Student class Faculty
Multiple Inheritance
While many languages do not allow multiple inheritance, C++ does! The basic concept is the same. You have multiple base classes, and a single class can derive from some or all of them, inheriting the data members and functions of each base class.
The Diamond Problem
An issue with multiple inheritance is the dreaded “diamond problem.” This comes about when a derived class inherits from two base classes, which each derive from the same parent base class. Consider this:
How to resolve the diamond problem
You can fix the issue of the diamond problem by using virtual inheritance. In the above example, the class declarations might look like this:
class Vehicle class Car : public Vehicle class Airplane : public Vehicle
class FlyingCar : public Car, public Airplane
With virtual inheritance, the “in-between” classes would add the virtual keyword to the inheritance:
class Car : virtual public Vehicle class Airplane : virtual public Vehicle
This ensures that FlyingCar would only inherit ONE instance of anything Car and Airplane inherited from Vehicle.
For more information:
https://en.wikipedia.org/wiki/Virtual_inheritance
https://stackoverflow.com/questions/2659116/how–does–virtual–inheritance–solve–the–diamond–multiple–inheritanceambiguit
Public and Private Inheritance
Perhaps the most common type of inheritance is public inheritance. It represents an “is-a” relationship between classes.
The derived class is a base class.
For example:
class Car : public Vehicle class SavingsAccount : public BankAccount class Button : public UIControl
We would say that the Car (derived class) IS A Vehicle (base class). A SavingsAccount IS A BankAccount. A Button IS A UIControl, etc.
If class inherits from another class using private inheritance, however, it models a “has a” relationship. This is also sometimes referred to as composition. For example:
// Private inheritance — the car “has a” Engine class Car : private Engine
The only thing the Car class can access of the Engine object is public members and functions — here’s where the accessor functions of a class would come into play.
Accessing members of base class(es)
To access the functions or variables of a base class, you must specify the name of the class, and then the thing you are trying to access. So in the case of the car and its engine, you would do something like this: void Car::SomeFunction()
{
string maker = Engine::GetManufacturer(); }
Reference: http://www.bogotobogo.com/cplusplus/private_inheritance.php
Polymorphism
One important concept in object-oriented programming is that a base-class pointer can point to any object which derives from that base class. For example:
class Car{}; // Base class
class Mustang : public Car{}; // Derived class
// Set a base class pointer to an instance of the derived class Car *someCar = new Mustang; // Valid due to inheritance
Following that same concept, we could extend that to something like the following:
Car *cars[3]; cars[0] = new Car; cars[1] = new SportsCar; cars[2] = new LuxurySedan;
What polymorphism allows you to do is call a function from one of those objects and depending on what the pointer is pointing to the correct function will be called.
class Car { public:
virtual void Identity() { cout << “I’m just a car!”; }
};
class SportsCar : public Car { public:
void Identity()
{ cout << “I’m a sports car!”; }
};
class LuxurySedan : public Car { public:
void Identity()
{ cout << “I’m a luxury car!”; }
};
Given the previous array, a line of code like the following would print out “I’m a sports car!”:
cars[1]->Identity(); // cars[1] points to a SportsCar
Polymorphism is the concept that allows you to write code like that, without knowing exactly what type of object you are pointing to. Since cars[1] points to a SportsCar, the call to Identity() will determine that the SportsCar version of the function should be used. The virtual keyword is required on the base class version of the function in order to make this functionality possible.
For more on polymorphism: http://www.cplusplus.com/doc/tutorial/polymorphism/
Bringing it all together
While it may seem like a lot of work to create such simple classes, once this framework is in place, you can very easily modify the class hierarchy with a minimal amount of additional effort (such as changing code elsewhere). Consider the following code:
Shape *shapes[] =
{
new Square(2.4f), new Rectangle(3, 5), new Triangle(4.2f, 6), new Circle(2), new Trapezoid(2, 6, 2.3f),
new Sector(2, 35)
};
for (int i = 0; i < 6; i++)
{
shapes[i]->Display(); cout << endl;
}
Nowhere in the loop is there any code checking the types of the objects. It simply uses a function, and because of inheritance, polymorphism, and virtual functions, each object behaves as it should. In this particular case the behavior of each object is simple, and very similar, but this concept allows us to change any individual object’s behavior, without modifying the code you see here. And if you were to write a function like this:
void SomeFunction(vector<Shape *> shapes)
{
for (unsigned int i = 0; i < shapes.size(); i++)
shapes[i]->Display();
}
You can further reduce the amount of information you need to have in order to write your code. How many objects are in the shapes vector? The size() function will tell us. What shapes, exactly, are in that vector? Don’t need to know, don’t need to care. The importance of this concept, of working with objects, WITHOUT KNOWING ALL OF THAT OBJECT’S DETAILS, cannot be overstated. Many things you will work on later in your programming career will rely on this concept.
Tips
A few tips about this assignment:
- Start with one class at a time. Since Square derives from Shape2D, which derives from Shape… start with the Shape class first. Then Shape2D, then Square, then all the other 2D shapes.
- Similarly, if a class like TriangularPyramid derives from both Shape3D and Triangle, it’s probably a good idea to complete those classes first.
- If you want to add any other functionality to these classes in order to help you get the job done, do it! Helper functions, constant values (pi, degrees to radian conversions, etc), go for it! The interface of the class indicates what it can do. It says nothing about how that gets done.
Useful Resources
- Inheritance in C++: https://www.geeksforgeeks.org/inheritance–in–c/
- Has-a vs. Is-a Terminology: https://stackoverflow.com/questions/2218937/has–a–is–a–terminology–in–objectoriented–language
[1] A similar thing; a duplicate