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

Recommended Answers

All 3 Replies

> 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

commented: Detailed, correct response! +1

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
}
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.