C++ virtual function undefined at link time - why?
Asked Answered
P

3

9

I'm having a bit of trouble using virtual functions in C++, and I might be misusing them in a constructor. The problem is that when linking a component lib (written by me) into my final executable, a virtual function is marked as undefined, even though I have written an implementation for it, and linked it.

I have the following class:

template<class BufferType, class ConnectionType, class HandlerType>
class UdpConnection
{
public:
UdpConnection(size_t dispatchCount) : service(),
        listener(service),
        pool(dispatchCount), sysMsgHandlers(),
        bufferPool(), buffers()
    {
        assert(dispatchCount > 0);
        initBuffers(dispatchCount);
        initSysHandlers();
    }
protected:
    virtual void initSysHandlers() = 0;
}

In my subclass:

class UdpClient : public UdpConnection<SyncBufferHandler, UdpClient, ClientNetworkHandler>
{
    protected:
        void initSysHandlers();
}

And the subclass source file:

void UdpClient::initSysHandlers()
{

}

As you can see, I am calling a virtual function in my constructor. As far as I can tell, this should be fine, since I am aware that my subclass constructor won't have been called, so I can't use any instance variables, but I simply add a few sub-class specific items to a std::map.

Linking CXX static library libnetwork.a
[ 75%] Built target network                                                                                           
Scanning dependencies of target testclient
[ 87%] Building CXX object CMakeFiles/testclient.dir/src/test/testclient.cpp.o                                        
Linking CXX executable testclient                                                                                     
src/network/libnetwork.a(udpclient.cpp.o): In function `voip::network::UdpConnection<voip::network::client::SyncBufferHandler, voip::network::client::UdpClient, voip::network::client::ClientNetworkHandler>::UdpConnection(unsigned long)':
udpclient.cpp:(.text._ZN4voip7network13UdpConnectionINS0_6client17SyncBufferHandlerENS2_9UdpClientENS2_20ClientNetworkHandlerEEC2Em[voip::network::UdpConnection<voip::network::client::SyncBufferHandler, voip::network::client::UdpClient, voip::network::client::ClientNetworkHandler>::UdpConnection(unsigned long)]+0x10d): undefined reference to `voip::network::UdpConnection<voip::network::client::SyncBufferHandler, voip::network::client::UdpClient, voip::network::client::ClientNetworkHandler>::initSysHandlers()'
collect2: ld returned 1 exit status

What am I doing wrong here? Please ask if you need more information, wanted to keep it as short as possible!

Pena answered 12/2, 2011 at 20:23 Comment(0)
P
21

You are calling the virtual function from the base class constructor. There are special rules for the dispatch of virtual functions during construction and destruction:

Effectively, when the constructor of the base class UdpConnection is executing, the dynamic type of the object is UdpConnection, not UdpClient, so the final overrider of the virtual function that is selected is the one for UdpConnection, not the most derived class, UdpClient.

This means that when you call initSysHandlers() in the UdpConnection constructor, it is UdpConnection::initSysHandlers() that gets called, ever time, not the override in the most derived class. Because you haven't provided a definition of UdpConnection::initSysHandlers(), you get the linker error.

The expert advice is that you should "Never Call Virtual Functions during Construction or Destruction".

Pageantry answered 12/2, 2011 at 20:28 Comment(3)
Alright, thanks! Thought it was something like that, but the error message was simply too non-descriptive (since I simply thought I couldn't use any members of the subclass). Sometimes I wish the compiler would add a bit more descriptive hints!Pena
@Max: Yeah, some compilers will helpfully warn you if you call a virtual function from a constructor. Other compilers are less helpful.Pageantry
I mistakenly clicked the down arrow, now it's fixed.. I finally earned my Critic Badge xDSagamore
S
3

Dont call virtual functions at the construction time. The construction goes from the base class to the derived class. So the constructor UdpConnection::UdpConnection does not yet know that the class has been inherited. The vtable for the derived class was not yet formed at this point, and the address of the overloaded function to call is not known.

Sagamore answered 12/2, 2011 at 20:26 Comment(2)
It's actually quite a bit trickier than "the vtable was not yet formed." There's a fun example involving calling a virtual function from an initialization list.Pageantry
I'm still pretty sure that this issue has something to do with vtable. The problem occurs during linking, when the object files are composed together in a uniform address space. Hope to find out more about c++ internals some day..Sagamore
H
2

You should never call virtual functions in a constructor or destructor as they do not have the expected behavior. The object has not been fully created until the constructor has return.

The base class constructor is called before the derived class constructor; therefore the base class data member and functions are created and defined first. So in your case, the UdpConnection ctor will try to call UdpConnection::initSysHandlers, NOT UdpClient::initSysHandlers because it has not be created yet. Since UdpConnection::initSysHandlers is pure virtual, it is therefore undefined at that point.

Hedveh answered 12/2, 2011 at 20:27 Comment(2)
Whether the call has the expected behavior depends on what you expect the behavior to be.Pageantry
Even it the behavior happens to appear to be what is expected, it would still be wrong.Hedveh

© 2022 - 2024 — McMap. All rights reserved.