2

How can I access sideA and height members of Triangle class, and how can I access sideA of a Square class, these are both derived from Shape class?

What is the correct way to implement that?

Shapes.h:

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

class Triangle : public Shape 
{
public:
    double sideA = 3;
    double height = 2;
    double getArea() {
        return 0.5 * sideA * height;
    }
};

class Square : public Shape 
{
public:
    double sideA = 4;

    double getArea() {
        return sideA * sideA;
    }
};

Main.cpp:

int main()
{
    Shape* sh = new Triangle();
    std::cout << sh->getArea() << std::endl;
    std::cout << sh->??? //get the height of triangle
    delete sh;
}
anastaciu
  • 23,467
  • 7
  • 28
  • 53
Max
  • 396
  • 4
  • 13

4 Answers4

3

You are trying to access information that is not available via the interface you defined, class Shape allows only the area to be accessed.

To get also the height, the proper way is to extend the interface to provide that information as well.

class Shape
{
public:
    virtual double getArea() = 0;
    virtual double getHeight() = 0;
};

class Triangle : public Shape 
{
public:
    double sideA = 3;
    double height = 2;
    double getArea() {
        return 0.5 * sideA * height;
    }
    double getHeight() {
        return height;
    }
};

class Square : public Shape 
{
public:
    double sideA = 4;

    double getArea() {
        return sideA * sideA;
    }
    double getHeight() {
        return sideA;
    }
};
rustyx
  • 80,671
  • 25
  • 200
  • 267
2

You can declare the variable a Triangle* not a Shape*, this way you'll have access to the derived class and base class methods and variables:

int main()
{
    Triangle* sh = new Triangle();
    Square* sh2 = new Square();
    std::cout << sh->getArea() << std::endl; //3
    std::cout << sh2->getArea() << std::endl; //16
    std::cout << sh->sideA << std::endl; //3
    std::cout << sh2->sideA << std::endl; //4
    delete sh;
}

To use delete sh safely you should have a virtual destructor

class Shape
{
public:
    virtual double getArea() = 0;
    virtual ~Shape(){} //virtual destructor
};

Since you already have an abstract class, why not use it to access the the data in the derived classes:

Here is how I would do it:

#include <iostream>
#include <memory>

class Shape
{  
private:
    double sideA; //shared members can be defined in base class, assuming all
                  //derived classes will have sideA member
protected:    
    Shape(double sideA) : sideA(sideA) {}//for initialization of sideA in derived classes
public:    
    Shape() = default;
    virtual double getArea() = 0;
    double getSideA() { //shared logic
        return sideA;
    }
    virtual ~Shape(){} //virtual destructor
};

class Triangle : public Shape 
{
private:
    double height = 2; //specific property
public:    
    Triangle() : Shape(3) {} //intialize sizeA
    double getHeight(){ //specific method, must instanciate Triangle to access
                        //for collections it's best to use interface method like getArea()
         return height;
    }
    double getArea() override {
        return 0.5 * getSideA() * height;
    }   
};

class Square : public Shape 
{
public:
    Square() : Shape(4) {} //intialize sizeA
    double getArea() override {
        return getSideA() * getSideA();
    }
};


int main()
{
    std::unique_ptr<Shape> sh(new Triangle); //smart pointer
    std::unique_ptr<Shape> sh2(new Square);  //smart pointer

    std::cout << sh->getArea() << std::endl; //3
    std::cout << sh2->getArea() << std::endl; //16
    std::cout << sh->getSideA() << std::endl; //3
    std::cout << sh2->getSideA() << std::endl; //4
    //delete sh; //no need, smart pointer
}

Take a look at smart pointers.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
2

Because your base class has a virtual function1, you can use the dynamic_cast conversion to check if a pointer to it is actually a pointer to one of its derived classes. This will return nullptr if it is not of the 'tested' class, or a usable pointer to the derived class, if it is:

int main()
{
    Shape* sh = new Triangle();
    std::cout << sh->getArea() << std::endl;
    if (dynamic_cast<Square*>(sh) != nullptr) { // Check for a valid Square pointer
        Square* sq = dynamic_cast<Square*>(sh);
        std::cout << sq->sideA << std::endl;
    }
    else if (dynamic_cast<Triangle*>(sh) != nullptr) { // Check for a valid Trianlge pointer
        Triangle* tr = dynamic_cast<Triangle*>(sh);
        std::cout << tr->height << std::endl;
    }
    else {
        std::cout << "Unspecified shape type: height unknown!" << std::endl;
    }
    delete sh;
    return 0;

1 Note that, because you have a virtual function in your Shape class, you should also give it a virtual destructor:

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

For further discussion on the need for a virtual destructor, see here: When to use virtual destructors?.


EDIT: In your specific case, the answer given by rustyx is really the 'correct' approach; however, it is useful to understand/appreciate the use of the dynamic_cast option, as this can be the only solution if you are deriving classes from a third-party base class, which you cannot modify, and thus cannot add the equivalent of the getHeight() function to it.
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
2

A Shape has no height. You are using the triangle polymorphically. That means you have a Shape* and can only use the interface of Shape, no matter what is the actual type of the object. If you want a Triangle then use a Triangle not a Shape. If you still want to use a Triangle and Rectangle polymorphically, then you should put the common interface into the base class. In your case, both have a sideA, so you could do:

struct Shape {
    double sideA = 3;
    virtual double getArea() = 0;
    virtual ~Shape(){}   
};

struct Triangle : public Shape {
    double height = 2;
    double getArea() {
        return 0.5 * sideA * height;
    }
};

struct Square : public Shape {
    double getArea() {
        return sideA * sideA;
    }
};

int main() {
    Shape* sh = new Triangle();
    std::cout << sh->sideA;
    delete sh;
}

PS: the above wasn't the whole truth. If you have a Shape* and you know that it is a Triangle* then you could use dynamic_cast, but doings such casts are often a sign for poor design. You should strive to write classes such that you do not need a cast.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Your 'PS' is a very good point *in this case* and your answer is excellent. However, there are cases when `dynamic_cast` is needed (see the 'EDIT' in my answer). – Adrian Mole Apr 25 '20 at 14:07
  • 1
    @ArianMole thanks and I think your answer explaining how to use `dynamic_cast` is an important contribution, I just thought that for someone new to polymorphism it could even be appropriate to mention the possibility to cast not at all (the PS really came as PS). However, as much as I agree with your answer as a whole, I don't buy your argument in the "EDIT", instead of the cast one can provide a base class with the desired interface and make that base class inherit from the third party class – 463035818_is_not_an_ai Apr 25 '20 at 14:15
  • You make a good argument. However, one 'real world' case that comes to mind is when deriving classes from the MFC `CDocument`. There are framework functions that return a native `CDocument*` pointer, and you often need to test which of your document types that points to. Adding an extra 'layer' won't help here, if the application can support document types that you didn't define. – Adrian Mole Apr 25 '20 at 14:19
  • @AdrianMole ah ok, I knew there was a hole in my point, but I didn't find it. Thanks for clarifying – 463035818_is_not_an_ai Apr 25 '20 at 14:22
  • Not really a *hole* in your argument ... indeed, some might even argue that you nicely demonstrated a *"sign for poor design"* in the MFC framework!! – Adrian Mole Apr 25 '20 at 14:25
  • 1
    @AdrianMole yeah well, the world isnt made of only good design, we have to live with it ;) – 463035818_is_not_an_ai Apr 25 '20 at 14:26