C++ - Tips'n Tricks - Using interface and abstract classes to compute the shape's area

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++.

Explanation

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).

Code

Makefile

## 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 *#

IShape.hh

#ifndef ISHAPE_HH
#define    ISHAPE_HH

class IShape {
public:
    virtual ~IShape() {};
    
    virtual void setArea(double y[]) = 0;
    virtual void setName() = 0;
};

#endif    /* ISHAPE_HH */

AShape.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 */

ShapeSquare.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 */

ShapeRectangle.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 */

ShapeTriangle.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 */

AShape.cpp

#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;
}

ShapeSquare.cpp

#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";
}

ShapeRectangle.cpp

#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";
}

ShapeTriangle.cpp

#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";
}

main.cpp

#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;
}

Compiling and running

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

Output

For argv[1] = 0

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

For argv[1] = 1

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

For argv[1] = 2

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

Finally

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! cool

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.