4

I have implemented different classes derived from an abstract class and each one has different methods. The problem is that I have to declare the object only at runtime, so I have to create a pointer to the base class and I can't use the methods of each derived class.

I have created an example to explain better what I mean:

#include <iostream>

using namespace std;

class poligon
{
public:
    double h, l;
    void setPoligon(double h, double l) {
        this->h = h;
        this->l = l;
    }
    virtual double GetArea() = 0;
    virtual void GetType() = 0;
};


class triangle : public poligon
{
    double GetArea() { return l*h / 2; }
    void GetType() { cout << "triangle" << endl; }
    double GetDiag() { return sqrt(l*l + h*h); }
};


class rectangle : public poligon
{
    double GetArea() { return l*h; }
    void GetType() { cout << "rectangle" << endl; }
};


void main()
{
    poligon* X;
    int input;

    cout << "1 for triangle and 2 for rectangle: ";
    cin >> input;

    if (input == 1)
    {
        X = new triangle;
    }
    else if (input == 2)
    {
        X = new rectangle;
    }
    else
    {
        cout << "Error";
    }

    X->h = 5;
    X->l = 6;

    X->GetType();
    cout << "Area = " << X->GetArea() << endl;

    if (input == 2)
    {
        cout << "Diangonal = " << X->GetDiag() << endl;    // NOT POSSIBLE BECAUSE " GetDiag()" IS NOT A METHOD OF "poligon" CLASS !!!
    }
}

Obviously the method X->GetDiag() at the end of the main can't be used because it is not a method of the "poligon" class. Which is the correct implementation of a program with this logic?

Christopher Oezbek
  • 23,994
  • 6
  • 61
  • 85
Mattia
  • 135
  • 3
  • 13

7 Answers7

3

Introduce a method in the base class

virtual bool boHasDiagonal(void) =0;

Declare unconditionally in base class:

virtual double GetDiag();

Implement it differently in both derived classes:

virtual bool boHasDiagonal(void) {return true;} // rectangle
virtual bool boHasDiagonal(void) {return false;} // triangle

Change output line:

if (X->boHasDiagonal())
{cout << "Diangonal = " << X->GetDiag() << endl;}

For a nice touch of paranoia (a healthy state of mind for a programmer in my opinion), use concept by Gluttton of a default implementation of GetDiag(), which signals an error (as in his answer here) .

For the case of many poligons, I like the proposal by Rakete1111 in the comment.

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
  • 2
    Because only some classes would override `boHasDiagonal` to return `true`, how about providing a default definition for `boHasDiagonal` in `polygon` to return `false`? That would save a lot of typing if you have a lot of polygons that don't have any diagonal. – Rakete1111 Sep 03 '17 at 08:19
  • @Rakete1111 Nice touch. I imagine GetDiag() could be semantically defined to mean "longest diagonal" and be implemented for precisely half of all poligons, but you are still right. Actually it could be "longest point to point" and be implemented for all, but that would be boring and against OPs intent, I guess. – Yunnosch Sep 03 '17 at 08:25
1

Define method in the base class which define implementation throws exception:

class poligon
{
public:
    virtual double GetDiag()
    {
        throw std::logic_error ("Called function with inappropriate default implementation.");
    }
};

In class that has meaningful implementation override it:

class rectangle : public poligon
{
    double GetDiag() override
    {
        return diagonale;
    }
};

Usage:

int main () {
    try {
        X->GetDiag();
    }
    catch (...) {
        std::cout << "Looks like polygon doesn't have diagonal." << std::endl;
    }
}
Gluttton
  • 5,739
  • 3
  • 31
  • 58
0

You can use dynamic_cast.

dynamic_cast<triangle*>(X)->GetDiag();

Note that you already have a bug: You only create a triangle if input == 1, but you get the diagonal if input == 2. Also, the above is not really safe, because dynamic_cast can return nullptr if the conversion is invalid.

But it would be better to check whether dynamic_cast succeeds, then you could also drop the input == 2 check:

if (triangle* tri = dynamic_cast<triangle*>(X))
    std::cout << "Diagonal = " << tri->GetDiag() << '\n';
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
0

You use dynamic_cast to access subclass-methods. It returns nullptr if it is not derived from the class. This is called down cast, as you are going down the class-tree:

triangle* R = dynamic_cast<triangle*>(X);
if(R) {
    cout << "Diagonale = " << R->GetDiag() << '\n';
};

Edit: You can put the declaration in the first line into the if-condition, which goes out of scope outside the if-statement:

if(triangle* R = dynamic_cast<triangle*>(X)) {
    cout << "Diagonale = " << R->GetDiag() << '\n';
};

if(rectangle* R = ...) {...}; // reuse of identifier

If you want to allow, that multiple subclasses have the GetDiag function you can inherit from the poligon-class and another diagonal-class. The diagonal-class only defines the GetDiag function and has not really to do with the polygon-class:

class polygon {
    // stays the same
};

class diagonal {
    virtual double GetDiag() = 0;
};

class triangle : public polygon, public diagonal {
    // body stays the same
};

And like above, you access the methods via casting with dynamic_cast but this time you cast to type diagonal. This time it is side cast, because poligon has nothing to do with diagonal, so you are going sideways in the tree.

   polygon         diagonal
    |   |             |
    |   |_____________|
    |          |
    |          |
rectangle   triangle
cmdLP
  • 1,658
  • 9
  • 19
0

Use dynamic casting to check if the base class' pointer is actually a triangle, like this:

int main()
{
    ...
    if(triangle* t = dynamic_cast<triangle*>(X))
        std::cout << "Triangle's diagonal = " << t->GetDiag() << std::endl;
    return 0;
}

PS: I assume that your example is just a draft, since it has some bugs.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
0

As others have said, you can use dynamic_cast to change the static type in your program, add a method to the base-class with a pseudo implementation or use some form of type-switching. However, I would consider all these answers as signs of a design flaw in your program and would reject the code. They all encode assumptions about the types existing in your program into the code and pose a maintenance burden. Imagine adding new types of shapes to your program. You then have to search and modify all the places you dynamic_cast your objects.

I think your example hierarchy is wrong in the first place. When you declare a base-class for ploygons, and derive triangles from it, the whole purpose of polymorphism is to be able to treat similar objects identically. So anything that is not common behavior (not implementation) is put in the base-class.

class poligon
{
public:
    double h, l;
    void setPoligon(double h, double l) {
        this->h = h;
        this->l = l;
    }
    virtual double GetArea() = 0;
    virtual void GetType() = 0;
};


class triangle : public poligon
{
    double GetArea() { return l*h / 2; }
    void GetType() { cout << "triangle" << endl; }
    double GetDiag() { return sqrt(l*l + h*h); }
};

You explicitly say that I can replace any instance of polygon with an instance of triangle everywhere in your program. This is the the Liskov substitution principle. What about circles? They don't have height and length. Can you use a rectangle everywhere you expect a polygon? Currently you can, but polygons can have more edges, be self-intersecting etc. I cannot add a new edge to a rectangle, otherwise it would be a rectangle anymore.

There are some solutions, but as it is a design question, the solution depends on what you want to do with the objects.

Jens
  • 9,058
  • 2
  • 26
  • 43
0

A downcast is usually a sign of a bad design and is rarely needed in practice.

I can't see why it is needed in this particular case. You have discarded the information about which type you have for no reason. An alternative could be:

void printDiagonal(const triangle& tri)
{
    std::cout << "Diangonal = " << tri.GetDiag() << std::endl;
}

void process(poligon& p)
{
    p.h = 5;
    p.l = 6;

    p.GetType();
    std::cout << "Area = " << p.GetArea() << std::endl;
}

int main()
{
    int input;

    std::cout << "1 for triangle and 2 for rectangle: ";
    std::cin >> input;

    if (input == 1)
    {
        triangle tri;
        process(tri);
        printDiagonal(tri);
    }
    else if (input == 2)
    {
        rectangle rect;
        process(rect);
    }
    else
    {
        std::cout << "Error\n";
    }
}

Live demo.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54