You're maybe wondering what are interface and abstract classes? That's a good point.
In this tutorial we're going to see how to compute the area of a shape by declaring an array of undefined shapes.
After this, we will be able to use this shape as an object with this kind of Factory design pattern.
Let's see how with this example of using interface and abstract classes in C++.
First of all, let's create two directories, one for the .hh (headers) and the other for the .cpp (bodies):
head/ body/
We have then:
In the head/:
head/IShape.hh head/AShape.hh head/ShapeRectangle.hh head/ShapeSquare.hh head/ShapeTriangle.hh
In the body/:
body/AShape.cpp body/ShapeRectangle.cpp body/ShapeSquare.cpp body/ShapeTriangle.cpp
In the root:
main.cpp Makefile
To summarize we have:
body/AShape.cpp body/ShapeRectangle.cpp body/ShapeSquare.cpp body/ShapeTriangle.cpp head/IShape.hh head/AShape.hh head/ShapeRectangle.hh head/ShapeSquare.hh head/ShapeTriangle.hh main.cpp Makefile
You are free to use the Makefile or not.
In the IShape interface, we've to implement only the destructor and the pure methods that the object must have.
The AShape inherits from the IShape. The AShape is the abtract class and can't be instancied. We don't need to redeclare the pure methods from the interface.
In the children of the AShape (ShapeRectangle, ShapeSquare and ShapeTriangle) we have to redeclare these pure methods but without being pure.
It means without a "= 0" at the end.
And so, these classes will be able to be instantied as objects.
Methods from these classes will be called by each object.
In our example, we redefine the setName() and setArea() methods.
Then each class has its own setName() and setArea().
The getName() and getArea() from the AShape will give us the name and the area of each object because the variable _name and _area are protected.
In the main.cpp, we create a function to see if the argument is correct (greater or equal to 0 and less or equal to 2).
Three arrays are created, one with only one element for the square (arrayA), one with two elements for the rectangle (arrayB) and the last with three elements for the triangle (arrayC).
Then we're declaring an AShapes' array to create our objects thanks to the argument (argv[x]).
After that we're creating an array of arrays to retrieve the correct array for computing the area of the shape we would like to see.
At the end, we're displaying and deleting the object created.
In the .cpp classes, we're using debug with std::cout to know what is created and deleted (C is for Constructor, CC for Copy Constructor and D for Destructor).
## Variables NAME = gogogo SRC = main.cpp \ body/AShape.cpp \ body/ShapeSquare.cpp \ body/ShapeRectangle.cpp \ body/ShapeTriangle.cpp \ OBJ = $(SRC:.cpp=.o) CPPFLAGS = -Werror -Wall -Wextra -Weffc++ -pedantic -ansi CC = g++ ## Rules $(NAME) : $(OBJ) $(CC) $(OBJ) -o $(NAME) all : $(NAME) clean : rm -f body/$(OBJ) fclean : clean rm -f $(NAME) re : fclean all r: re rm -f *.o rm -f *~ rm -f *#
#ifndef ISHAPE_HH #define ISHAPE_HH class IShape { public: virtual ~IShape() {}; virtual void setArea(double y[]) = 0; virtual void setName() = 0; }; #endif /* ISHAPE_HH */
#ifndef ASHAPE_HH #define ASHAPE_HH #include <string> #include "IShape.hh" class AShape : public IShape { public: AShape(); AShape(const AShape& orig); virtual ~AShape(); double getArea() const; std::string getName() const; protected: double _area; std::string _name; }; #endif /* ASHAPE_HH */
#ifndef SHAPESQUARE_HH #define SHAPESQUARE_HH #include <string> #include "AShape.hh" class ShapeSquare : public AShape { public: ShapeSquare(); ShapeSquare(const ShapeSquare& orig); virtual ~ShapeSquare(); void setArea(double theArray[]); void setName(); }; #endif /* SHAPESQUARE_HH */
#ifndef SHAPERECTANGLE_HH #define SHAPERECTANGLE_HH #include <string> #include "AShape.hh" class ShapeRectangle : public AShape { public: ShapeRectangle(); ShapeRectangle(const ShapeRectangle& orig); virtual ~ShapeRectangle(); void setArea(double theArray[]); void setName(); }; #endif /* SHAPERECTANGLE_HH */
#ifndef SHAPETRIANGLE_HH #define SHAPETRIANGLE_HH #include "AShape.hh" class ShapeTriangle : public AShape { public: ShapeTriangle(); ShapeTriangle(const ShapeTriangle& orig); virtual ~ShapeTriangle(); void setArea(double theArray[]); void setName(); }; #endif /* SHAPETRIANGLE_HH */
#include "../head/AShape.hh" #include "../head/ShapeSquare.hh" #include "../head/ShapeRectangle.hh" #include <iostream> #include <string> AShape::AShape() : _area('\0'), _name("Abstract Shape") { std::cout << "C AShape" << std::endl; } AShape::AShape(const AShape& orig) : _area('\0'), _name("Abstract Shape") { (void)(orig); std::cout << "CC AShape" << std::endl; } AShape::~AShape() { std::cout << "D AShape" << std::endl; } double AShape::getArea() const { return this->_area; } std::string AShape::getName() const { return this->_name; }
#include <iostream> #include "../head/ShapeSquare.hh" ShapeSquare::ShapeSquare() { std::cout << "C ShapeSquare" << std::endl; } ShapeSquare::ShapeSquare(const ShapeSquare& orig) : AShape(orig) { std::cout << "CC ShapeSquare" << std::endl; } ShapeSquare::~ShapeSquare() { std::cout << "D ShapeSquare" << std::endl; } void ShapeSquare::setArea(double side[]) { this->_area = side[0] * side[0]; } void ShapeSquare::setName() { this->_name = "Square"; }
#include <iostream> #include "../head/ShapeRectangle.hh" ShapeRectangle::ShapeRectangle() { std::cout << "C ShapeRectangle" << std::endl; } ShapeRectangle::ShapeRectangle(const ShapeRectangle& orig) : AShape(orig) { std::cout << "CC ShapeRectangle" << std::endl; } ShapeRectangle::~ShapeRectangle() { std::cout << "D ShapeRectangle" << std::endl; } void ShapeRectangle::setArea(double side[]) { this->_area = side[0] * side[1]; } void ShapeRectangle::setName() { this->_name = "Rectangle"; }
#include <iostream> #include <cmath> #include "../head/ShapeTriangle.hh" ShapeTriangle::ShapeTriangle() { std::cout << "C ShapeTriangle" << std::endl; } ShapeTriangle::ShapeTriangle(const ShapeTriangle& orig) : AShape(orig) { std::cout << "CC ShapeTriangle" << std::endl; } ShapeTriangle::~ShapeTriangle() { std::cout << "D ShapeTriangle" << std::endl; } void ShapeTriangle::setArea(double side[]) { double sumSides = side[0] + side[1] + side[2]; double semiPerim = sumSides / 2; double areaHeron = sqrt( (semiPerim) * (semiPerim - side[0]) * (semiPerim - side[1]) * (semiPerim - side[2]) ); this->_area = areaHeron; } void ShapeTriangle::setName() { this->_name = "Triangle"; }
#include <cstdlib> #include <iostream> #include "head/AShape.hh" #include "head/ShapeSquare.hh" #include "head/ShapeRectangle.hh" #include "head/ShapeTriangle.hh" using namespace std; int main(int argc, char** argv) { if (argc != 2 || (atoi(argv[1]) < 0 || atoi(argv[1]) > 2)) { cout << "Usage: ./gogogo [0-2]." << endl; return 1; } double arrayA[] = {7, '\0'}; double arrayB[] = {5, 6, '\0'}; double arrayC[] = {24, 30, 18, '\0'}; AShape *arr[] = { new ShapeSquare(), new ShapeRectangle(), new ShapeTriangle(), '\0' }; int index = atoi(argv[1]); double *arrOfArray[] = { arrayA, arrayB, arrayC }; arr[index]->setArea(arrOfArray[index]); arr[index]->setName(); cout << " From main, arr[i]->getName() = " << arr[index]->getName() << endl; cout << " From main, arr[i]->getArea() = " << arr[index]->getArea() << endl; int i = 0; i = 0; while (arr[i] != '\0') { delete arr[i]; ++i; } return 0; }
We've only to use an argument (argv[x]) for the binary.
For example:
make r ; ./gogogo 0
or
make r ; ./gogogo 1
or
make r ; ./gogogo 2
C AShape C ShapeSquare C AShape C ShapeRectangle C AShape C ShapeTriangle From main, arr[i]->getName() = Square From main, arr[i]->getArea() = 49 D ShapeSquare D AShape D ShapeRectangle D AShape D ShapeTriangle D AShape
C AShape C ShapeSquare C AShape C ShapeRectangle C AShape C ShapeTriangle From main, arr[i]->getName() = Rectangle From main, arr[i]->getArea() = 30 D ShapeSquare D AShape D ShapeRectangle D AShape D ShapeTriangle D AShape
C AShape C ShapeSquare C AShape C ShapeRectangle C AShape C ShapeTriangle From main, arr[i]->getName() = Triangle From main, arr[i]->getArea() = 216 D ShapeSquare D AShape D ShapeRectangle D AShape D ShapeTriangle D AShape
A great exercise in order to understand what is an interface, an abtract class and how to use them in C++.
Well done, it wasn't easy but you've made it.
Good job!
Add new comment