Loek Janssen
Loek Janssen

Reputation: 451

Abstract class inheritance and smart pointer containers

I am creating an abstract geometry class that has children classes. However, I want that the class RightCircularCone also has its own private variables that define its apex coordinates, such that the abstract class is not unnecessary big in memory size for objects of type Sphere that don't need storage of apex variables. However, I can't seem to access the functions and variables of RightCircularCone when I load them from a container that uses smart pointers, as it keeps being defined as its parent class Shape. Can anyone see what is going wrong?! Appreciate it!

/* shapes.hpp */
class Shape{
    public:
        unsigned int color;
        float radius;
        float x,y,z;
    public:
        void SetSpatial(float radius, float x, float y, float z);
        unsigned int GetColor(void);
        void SetColor(unsigned int color);
        virtual bool PointInside(const std::array<double,3> &point)=0;
};

class RightCircularCone : public Shape{ 
    private:
        float Ax,Ay,Az;
    public:
        bool PointInside(const std::array<double,3> &point);
        void SetApex(float x, float y, float z);
        void PrintApex();
};

class Sphere : public Shape{
    public:
        bool PointInside(const std::array<double,3> &point);
};

The classes defined above are used in another .cpp file where methods of a class are defined:

#include "../../include/part.hpp" /* includes shapes.hpp in turn */

void Part::ReadPartFile(std::string partfile){
    try{
        std::ifstream dataFile;
        dataFile.open(partfile);
        //do checks, error badbits etc...
        std::string word;
        unsigned int counter=0;
        while(!dataFile.eof()){
            dataFile >> word;
            if(word == "sphere"){
                auto newSphere = std::make_shared<Sphere>();
                // load variables into objects from file by checking each word by using setColor and setSpatial
                shapeList[counter++] = newSphere;
            } else if(word == "rccone"){
                auto newRccone = std::make_shared<RightCircularCone>();
                // load variables into objects from file by checking each word by using setColor and setSpatial and setApex
                shapeList[counter++] = newRccone;
            }
        }
        dataFile.close();
    } catch(std::ifstream::failure e) {
        //do exception handling here if necessary
}

Now, when I use an iterator over the map std::map<unsigned int, std::shared_ptr<Shape> > shapeList; as defined in part.cpp I can never access the methods of children classes Sphere and RightCircularCone as the map returns a type of Shape, even though I used a smart pointer!!! Anybody knows why and a potential fix (or neater way to set up the classes)?? Thanks!

//EDIT:

This is the error I get:

error: no member named 'PrintApex' in 'Shape'
                                iterator->second->PrintApex();

Upvotes: 1

Views: 1171

Answers (1)

Tasos Vogiatzoglou
Tasos Vogiatzoglou

Reputation: 2453

Think about it. You create a vector of shared_ptr of Shape. As far as the vector is concerned you are storing Shape instances in it, not Sphere or whatever.

Now, you happen to initialize your Shape instance with a Sphere and you store the ptr of that Sphere into the vector. Next item is a RightCircularCone, again stored as a Shape.

You access the first element, and as far as the compiler is concerned at THAT point, you only have a Shape. It cannot deduce the actual type as this happens at runtime. So, as far as the compiler is concerned you have a Shape instance with whatever the Shape class contains.

Now, you need to somehow inform the compiler about the type you want to work with, so it can find the methods you want to access. For that, you use dynamic_cast to specify the intent that this is a Sphere and you should have access to Sphere members.

More details about dynamic_cast, here http://en.cppreference.com/w/cpp/language/dynamic_cast

Edit. A note about design.

In principle, you want to offload as much to the compiler. If there is something that compiler can do, let him do it. So, if you want to do something different for each subclass of Shape, instead of checking a string literal for the Shape type you could use virtual specialized functions that act on a specific type. E.g.

virtual void printMe(Sphere & sphere) {
cout << "This is a sphere with radious x" << endl;
} 

virtual void printMe(RightCircularCone & cone) {
cout << "This is a cone" << endl;
}

//and you use like 

for (auto & shape: shapes) { printMe(shape); } 

//and the correct functions are resolved automagically

it may seem a bit more work, but in the long run it is actually simpler as you offload the picking of the functionality to someone else.

Upvotes: 1

Related Questions