Search code examples
c++definitionvirtual-functionsundefined-reference

What's the difference between these two definitions of a virtual function?


I was getting an error in something called TouchGFX and it was pointed out to me that a virtual function inside a class needed to be defined with curly braces vs just a semicolon at the end of its name. I can do basic C++ but inheritance, virtual functions, etc are not something I use often.

What's the difference between #1 and #2 way to define the virtual function setTimeDate()?

TouchGFX allows you to create "screens" for the display. Like your cellphone display. Turn it on and there is a screen. If you swipe or open an app you get a different screen.

There are two classes that are for all screens (model and modelListener). Each screen in the project gets two more classes (Presenter and a View). I showed below the classes for the principalScreen but there is another ... SecondScreen. There are no virtual functions defined in the SecondScreen ... I haven't gotten to that part of the code yet. The error I was getting was:

./Application/User/gui/SecondScreenPresenter.o:(.rodata._ZTV21SecondScreenPresenter+0x28):undefined reference to `ModelListener::setTime(displayTime_t)'

osMessageQueueGetCount, osMessageQueueGet are part of [CMSIS-RTOS2](https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSIS__RTOS__Message.html).

I only changed the definition inside the model class and the error went away.

virtual void setTime(displayTime_t time);   // #1 error

virtual void setTime(displayTime_t time){}  // #2 correct
class ModelListener
{
    public:
        ModelListener() : model(0) {}

        virtual ~ModelListener() {}

        void bind(Model* m)
        {
            model = m;
        }

        virtual void setTimeDate(displayTimeDate_t nTimeDate){}

    protected:
        Model* model;

};
Model::Model() : modelListener(0), newTimeDate({0})
{

}

void Model::tick()
{
    if(osMessageQueueGetCount(qRTCtoUIHandle)>0)
    {
        if(osMessageQueueGet(qRTCtoUIHandle, &newTimeDate, 0, 0)==osOK)
        {
            modelListener->setTimeDate(newTimeDate);
        }
    }
}
class principalScreenPresenter : public touchgfx::Presenter, public ModelListener
{
    public:
        principalScreenPresenter(principalScreenView& v);
    
        /**
         * The activate function is called automatically when this screen is "switched in"
         * (ie. made active). Initialization logic can be placed here.
         */
        virtual void activate();
    
        /**
         * The deactivate function is called automatically when this screen is "switched out"
         * (ie. made inactive). Teardown functionality can be placed here.
         */
        virtual void deactivate();
    
        virtual ~principalScreenPresenter() {};
    
        virtual void setTimeDate(displayTimeDate_t nTimeDate);
    
    private:
        principalScreenPresenter();
    
        principalScreenView& view;
};
class principalScreenView : public principalScreenViewBase
{
    public:
        principalScreenView();
        virtual ~principalScreenView() {}
        virtual void setupScreen();
        virtual void tearDownScreen();

        virtual void setTimeDate(displayTimeDate_t nTimeDate);

    protected:

};

I was following a YouTube video to do this, but using a struct instead of an int like in the video, I thought I accidentally copy/pasted the function in a different file. But after a few hours of trying stuff, I discovered that it was related to a screen interaction. I couldn't figure the error out on my own.

I asked in the STM forum and someone gave me the answer. I'm trying to understand more about the solution. My C++ book (very old) talks about virtual functions, but not this small difference. There is lots of stuff online, but I could find an explanation for this.


Solution

  • Your problem is not related to virtual functions and is not even specific to C++ (the same applies to C).

    What's the difference between #1 and #2 way to define the virtual function setTimeDate?

    virtual void setTime(displayTime_t time);   // #1 error
    
    virtual void setTime(displayTime_t time){}  // #2 correct
    

    The difference is that #1 is only a function prototype, not a definition. It assumes the function is defined elsewhere. If you don't provide the function definition anywhere, the linker produces "undefined reference" error.

    In #2, you provided a function body{}, which defines an empty (aka no-op) function.

    Specifically with virtual functions, you may specify that it is pure, i.e., it does not have any implementation, by adding =0 at the end:

    virtual void setTime(displayTime_t time) = 0;
    

    However, if it is included in a class, this makes the class abstract, i.e., you cannot create a variable of this class. It can only serve as a base class.