Hello everybody

I thought I understood inheritance but it turns out I don't.

I am creating an SDL/OpenGL mini-GUI library.
I have a master class, GUIComponent, with subclasses such as TextLabel under it.

I have a vector called guiList that is initialized as std::vector<GUIUnit*> guiList (it's extern'd and initialized elsewhere; it's not a visibility problem). It contains pointers to a custom class called GUIUnit.

These GUIUnits have a function, getComponent(int) , that retrieves the argument's indexed value from a vector of pointers to GUIComponents. That too, works.

I have a subclass of GUIComponent called TextLabel. I can add them to the vector with no problem. However, I want to use the call guiList[0]->getComponent(1)->getFont() to get a %s-thingy that I can use for printf() debugging output. However, I get a compilation error that the class GUIComponent has no member getFont(). However, TextLabel does, and getComponent(1) returns a GUIComponent that is actually a TextLabel.

If anybody can understand me I have two questions.

  1. Is this legal? (For example, can I set a GUIComponent as the return of a function and instead return a subclass?)
  2. What would work?
  3. Why is the compiler complaining?

Thank you very much in advance and have a happy New Year.
epicbeast9022

> s this legal? (For example, can I set a GUIComponent as the return of a function and instead return a subclass?)

Yes. For example:

GUIComponent* getComponent() { return new TextLabel( /* ... */ )

is fine, provided TextLabel is a derived class of GUIComponent.


> Why is the compiler complaining?

The compile-time type of the result of the function is a pointer to GUIComponent (though the run-time of the pointed object might be TextLabel). And the compiler cannot see a member-function getFont() in GUIComponent.


> What would work?

A simple way would be to perform a type-safe run-time down-cast to TextLabel and call getFont() on the result of the cast. For example:

GUIComponent* gui_component = guiList[0]->getComponent(1) ;

TextLabel* text_label = dynamic_cast<TextLabel*>(gui_component) ;

if( text_label != nullptr ) 
{
   // use text_label
   auto font = text_label->getFont() ;
   // etc
}
else
{
   // this component is not a TextLabel 
}

See: http://www.bogotobogo.com/cplusplus/dynamic_cast.php

Comments
Detailed, correct response!

Hello vijayan121,

Thank you so much for the detailed response! It fixed my problem.

(For any browsing user, for this to work the base class, here GUIComponent, must have a virtual function - since mine doesn't need it, I just shut down the error with

virtual void aWheatleyFix()

(if anyone gets it), and made the code simply a comment.)

> for this to work the base class, here GUIComponent, must have a virtual function -
> since mine doesn't need it, I just shut down the error with ...

You need a virtual destructor; C++ requires that a class having a virtual function must have a virtual destructor. Even if there was no dynamic_cast and no other virtual functions, you would still require a virtual destructor if you do something like this:

std::vector<GUIComponent*> components ;
components.push_back( new TextLabel( /*...*/ ) ) ; // etc

// use the vector
// ...

// finaly clean up
// the correct destructor (and operator delete) must be called here.
for( component* c : components ) delete c ;

Consider using smart pointers instead of raw pointers whenever clean up is required; for instance if GUIComponent objects were dynamically allocated with new.

std::vector< std::unique_ptr<GUIComponent> > or std::vector< std::shared_ptr<GUIComponent> > would take care of resource management. See:
http://www.devx.com/cplus/10MinuteSolution/28347/0/page/1
http://www.devx.com/cplus/10MinuteSolution/39071/1954

Smart pointers are handy whenever acquisition and release of resources are an issue - for example when using a C-library (may be SDL) with low-level facilities (sandwich functions) for resource management. For example:

void function( /*...*/ ) // pseudocode
{
    // ... ;

    std::shared_ptr<std::FILE> file( std::fopen( __FILE__, "r" ), std::fclose ) ;
    // the destructor of shared_ptr will call std::fclose

    // ...

    foo( /* ... */ ) ; // safe evn if foo throws; file will be closed

    // ...

    if( nothing_more_2b_done ) return ; // safe: file will be closed

    // ...

    int c ;
    while( ( c = std::fgetc( file.get() ) ) != EOF ) std::cout << char(c) ;

    throw "something" ; // safe: file will be closed

    // ...
    
    // normal return :  file will be closed
}
This question has already been answered. Start a new discussion instead.