17

EDIT: I have heavily edited this post to strip the project down to its essentials. I have also added a Github repository, including the files which are not referenced in this post.


I have a Qt Creator project (qmake, Qt 5.2.0, Creator 3.0.0) that uses the subdirs template. There are three subprojects:

  1. Stadium - library that is configured as TEMPLATE = lib and CONFIG += staticlib.
  2. Football - library that is configured as TEMPLATE = lib and CONFIG += staticlib and uses the Field library.
  3. Server - a QML application that uses both Stadium and Football libraries.

I'm building this application on both Windows 8.1 (MSVC2012) and Linux (gcc 4.8.1). It works without issue on Windows, but the Linux build behaves strangely.

The errors I get look like this:

undefined reference to 'vtable for Stadium::Engine'

I've rewritten this project to a set of bare files that displays the error. You can find it on Github here: Football. Feel free to clone it and see all the errors for yourself. The 661441c commit solves the problem, and the 09836f9 commit contains the errors.

The Stadium Engine.h file is an abstract class. It looks like this:

#ifndef STADIUM_ENGINE_H
#define STADIUM_ENGINE_H

#include <QObject>

namespace Stadium {

class Engine : public QObject
{
    Q_OBJECT

public slots:
    virtual void executeCommand() = 0;

};

} // namespace Stadium

#endif // STADIUM_ENGINE_H

Here is the Football Engine.h file, which inherits from the Stadium Engine.h file above:

#ifndef FOOTBALL_ENGINE_H
#define FOOTBALL_ENGINE_H

#include <QObject>
#include "../Stadium/Engine.h"

namespace Football
{

class Engine : public Stadium::Engine
{
    Q_OBJECT

public:
    Engine();
    ~Engine() {}

public slots:
    void executeCommand();

};

} // namespace Football

#endif // FOOTBALL_ENGINE_H

And the Football Engine.cpp file:

#include "Engine.h"

#include <QDebug>

Football::Engine::Engine()
{
    qDebug() << "[Football::Engine] Created.";
}

void Football::Engine::executeCommand()
{
    qDebug() << "[Football::Engine] The command was executed.";
}

If I move the constructor definition from the cpp to the header file, it builds without error.

Below is the the Server.pro file. It's indicative of all my other pro files, in that the static linking descriptions (auto-generated by Qt Creator) look the same.

QT       += core

QT       -= gui

TARGET = Server
CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app


SOURCES += main.cpp

win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../Stadium/release/ -lStadium
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../Stadium/debug/ -lStadium
else:unix: LIBS += -L$$OUT_PWD/../Stadium/ -lStadium

INCLUDEPATH += $$PWD/../Stadium
DEPENDPATH += $$PWD/../Stadium

win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Stadium/release/libStadium.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Stadium/debug/libStadium.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Stadium/release/Stadium.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Stadium/debug/Stadium.lib
else:unix: PRE_TARGETDEPS += $$OUT_PWD/../Stadium/libStadium.a

win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../Football/release/ -lFootball
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../Football/debug/ -lFootball
else:unix: LIBS += -L$$OUT_PWD/../Football/ -lFootball

INCLUDEPATH += $$PWD/../Football
DEPENDPATH += $$PWD/../Football

win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Football/release/libFootball.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Football/debug/libFootball.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Football/release/Football.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../Football/debug/Football.lib
else:unix: PRE_TARGETDEPS += $$OUT_PWD/../Football/libFootball.a

I've tried cleaning, re-running qmake, removing the build directory, and rebuilding. The only way to get this project build in Linux is to remove the CONFIG += staticlib line in the .pro file of the Stadium library (and the corresponding else:unix: PRE_TARGETDEPS += $$OUT_PWD/../stadium/libstadium.a line in the Game.pro too, of course). This builds the project successfully and runs without issue. But I just don't understand why. I also don't understand why it matters where the constructor definition is defined.

Any ideas?

Ali
  • 56,466
  • 29
  • 168
  • 265
jmbeck
  • 938
  • 2
  • 11
  • 21
  • I see this when you need to run qmake again – paulm Feb 05 '14 at 16:51
  • @paulm, that's right, but it doesn't seem to work. I am beginning to suspect there's a bug in the build process. – jmbeck Feb 06 '14 at 07:42
  • Show the code which inherits from Engine – paulm Feb 06 '14 at 08:29
  • @paulm, your implied suggestion was at least partially correct. :) The code that inherited from Engine had an extra Q_OBJECT macro (unnecessary, because the inherited class already had that). The odd thing is that the build process never flagged the error in the inherited class. I'm still trying to figure out exactly which of the 15 things I tried, helped, but I'll post the solution soon. – jmbeck Feb 08 '14 at 09:55
  • sometimes msvc or clang can give more meaningful errors - probably not in this case though but might be worth a try – paulm Feb 08 '14 at 12:26
  • That's the weird thing; it compiles and runs just fine in MSVC. This is _only_ a gcc/Linux thing. – jmbeck Feb 08 '14 at 18:21
  • The `vtable` thing hints at a missing object/class definition. Enable *all* warnings, and go over them carefully. It might be a standard missmatch between compilers? Clean all generated files, also clean out `~/.ccache`, and build from scratch. – vonbrand Mar 03 '14 at 03:22

5 Answers5

15

The answer is disappointingly simple: the libraries were linked in the wrong order.

I looked at the command that invoked the linker (right above the linker error):

g++ [...] -lStadium [...] -lFootball 

I also looked into the code: The Football subproject refers to the Stadium subproject, so the libraries are in the wrong order, see for example the accepted answer of GCC C++ Linker errors: Undefined reference to 'vtable for XXX', Undefined reference to 'ClassName::ClassName()' for explanation.

Indeed, if I swap the two libraries in the Server.pro file (derived from commit 09836f9, irrelevant win32 specific details deleted for brevity):

[...]

SOURCES += main.cpp

LIBS += -L$$OUT_PWD/../Football/ -lFootball
INCLUDEPATH += $$PWD/../Football
DEPENDPATH += $$PWD/../Football
PRE_TARGETDEPS += $$OUT_PWD/../Football/libFootball.a

LIBS += -L$$OUT_PWD/../Stadium/ -lStadium
INCLUDEPATH += $$PWD/../Stadium
DEPENDPATH += $$PWD/../Stadium
PRE_TARGETDEPS += $$OUT_PWD/../Stadium/libStadium.a

Now the command line looks like:

g++ [...] -lFootball [...] -lStadium

It compiles and runs just fine on my Linux machine.

Community
  • 1
  • 1
Ali
  • 56,466
  • 29
  • 168
  • 265
  • 2
    You've GOT to be kidding me. I can't believe the gcc linker is so simplistic. I've been programming in Java too long. I changed the order in my original project, and it worked. The sad thing is, I intentionally added the libraries in that order because it just 'made sense to put the non-dependent library first'. Sheesh. Thanks for your help. I learned something new today. – jmbeck Feb 09 '14 at 19:26
  • 1
    @jmbeck I am glad it helped! :) I guess the linker is simplistic because it makes the implementation of the linker simpler and the linking process faster. The link times can be significant on very large projects, for example building clang from source. – Ali Feb 09 '14 at 21:13
0

You have inlined the virtual destructor.
This can sometimes lead to problems.
Try to implement the destructor in a .cpp file. I also would remove the = 0 from the declaration of the destructor.

Kurt Pattyn
  • 2,758
  • 2
  • 30
  • 42
  • The inlined destructor was advice from somone who thought it might help. Removing it doesn't affect the error. – jmbeck Feb 02 '14 at 20:59
  • (ran out of edit time): Unfortunately, removing the destructor to a CPP file (or removing it entirely), does not change anything. I tried making it non-pure too, but it didn't help. – jmbeck Feb 02 '14 at 21:10
0

You can take a look at this other questions:

I tried compiling your STADIUM_ENGINE code as an static lib and then linking from an app, and I've just got the error when NO virtual pure destructor is defined (as expected). If you don't have a virtual destructor defined you won't be able to instantiate any derived class.

Anyhow, your class inherits from QObject, which already declares and implements a non pure virtual destructor. Is it useful a pure virtual destructor?

Community
  • 1
  • 1
kikeenrique
  • 2,589
  • 2
  • 25
  • 46
0

Okay, I figured out the solution. I had three distinct issues that when changed, cleared the vtable errors. Unfortunatly, I don't have any idea why the second two changes are necessary.

1. Q_OBJECT in the derived class

The class which inherited the Stadium::Engine class above, had an extra Q_OBJECT inside. When I removed the second Q_OBJECT in the derived class, one of the vtable errors went away.

2. The Engine constructor

I do not understand why, but when the derived class defined the constructor in the CPP file, it gives vtable errors. When defined in the header (inside the class description), it works fine. There is nothing in the constructor (DerivedEngine() {}). I can't figure out why this would matter.

3. Define constructor and virtual destructor

It is required that both the constructor and destructor are defined in the pure abstract class. I do not understand why. I added these lines in the header, outside the class definition:

inline Stadium::Engine::Engine() {}
inline Stadium::Engine::~Engine() {}

This still really bothers me. Why are these changes necessary? Why does this only happen with gcc/Linux? Surely this is a Qt bug, right?

jmbeck
  • 938
  • 2
  • 11
  • 21
  • @Ali, I've completely rewritten my initial post, and boiled down the project to a minimum set of files. You can download the files from Github (see the original post). At this point, I'm interested in knowing why I have to move the constructor to the header. – jmbeck Feb 09 '14 at 17:26
-1

I do not see where you run the moc compiler. moc creates a file that resolves additional things for your QObject derived classes.

If moc is running, your namespace may be the problem. moc is not known to work well with namespaces. I like namespaces, but Qt is been there before people started using them everywhere.

Removing the Q_OBJECT and derivation from QObject is another solution, if that's not absolutely required for that class.

Another possibility is that your makefiles are out of date. In that case you'd want to force a run of qmake to make sure they are refreshed properly.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
  • The moc compiler seems to be run automatically when Qt Creator builds the project. The .moc files appear in the build directory. The namespace issue could be a problem, but it builds just fine in Windows. So is it a GCC/MSVC issue, or a Qt build process issue? Maybe Qt doesn't give gcc the proper paths in Linux? I can't even guess how to figure that out. Q_OBJECT is required, and as mentioned, I've rebuilt using qmake (clean, deleting build directory, etc) many times. – jmbeck Feb 06 '14 at 07:47
  • The moc is run by the makefile generated by qmake. The `.pro` file is an input to qmake. Your answer is wrong on almost every count. – Kuba hasn't forgotten Monica Feb 06 '14 at 08:30
  • QObject is not necessary if the class is not used as a child or parent of another object. The "good thing" about having a QObject that you make a child of another object is that the delete is automatic. To know whether moc ran properly, you should see a .cxx file in your build directory for each header file you have in your qmake file. – Alexis Wilke Feb 06 '14 at 21:42