Why my project doesn't link if there are Q_OBJECT macros in .cpp files?
Question
This code compiles, links and works as intended:
#include <QApplication>
#include <QListView>
#include "File_List_Model.h"
int main(int c,char**v)
{
QApplication app(c,v);
QStringList list;
list << "a" << "b" << "c";
File_List_Model* model = new File_List_Model;
model->set_entries(list);
QListView* view = new QListView;
view->setModel(model);
view->show();
return app.exec();
}
But when I put the class definition in .cpp
file instead of header files, I get linker errors stating that vtable
was not properly defined.
#include <QApplication>
#include <QListView>
//#include "File_List_Model.h"
#include "File_List_Proxy.h"
#include <QAbstractItemModel>
#include <QStringList>
class File_List_Model : public QAbstractItemModel
{
Q_OBJECT
private:
QStringList data_;
public:
File_List_Model(QObject *parent = 0) :
QAbstractItemModel(parent)
{
}
int columnCount(const QModelIndex& parent) const
{
return 1;
}
QVariant data(const QModelIndex & index, int role) const
{
switch(role)
{
case Qt::DisplayRole:
return data_[index.row()];
default:
return QVariant();
}
}
QModelIndex index(int row, int column, const QModelIndex & parent) const
{
return createIndex(row,column);
}
QModelIndex parent(const QModelIndex & index) const
{
return QModelIndex();
}
bool set_entries(const QStringList& entries)
{
if (entries.size())
{
beginInsertRows(createIndex(0,0),0,entries.size());
data_ = entries;
endInsertRows();
emit dataChanged(createIndex(0,0),createIndex(0,entries.size()));
return true;
}
else
{
return false;
}
}
int rowCount(const QModelIndex & parent) const
{
return data_.size();
}
};
int main(int c,char**v)
{
QApplication app(c,v);
QStringList list;
list << "a" << "b" << "c";
File_List_Model* model = new File_List_Model;
model->set_entries(list);
File_List_Proxy* proxy = new File_List_Proxy;
proxy->setSourceModel(model);
QListView* view = new QListView;
view->setModel(proxy);
view->show();
return app.exec();
}
//error:
debug/moc_File_List_Model.o:moc_File_List_Model.cpp:(.rdata$_ZTV15File_List_Model[vtable for File_List_Model]+0x44): undefined reference to `File_List_Model::columnCount(QModelIndex const&) const'
debug/moc_File_List_Model.o:moc_File_List_Model.cpp:(.rdata$_ZTV15File_List_Model[vtable for File_List_Model]+0x4c): undefined reference to `File_List_Model::data(QModelIndex const&, int) const'
This seems to be exactly the same code. Why does it link when the code is in headers and it doesn't link otherwise?
Solution
Qt uses moc
tool to handle C++ extensions that a required, for example, for signals-slots mechanism. This tool processes all header (!) files in the project and generates new source files that contain meta-object code for those classes that contain Q_OBJECT
macro.
When you have your class defined in .cpp
file instead of .h
file moc
fails to process it properly.
Have a look at this article for more information about Qt Meta-Object Compiler.
OTHER TIPS
Because Qt runs moc on header files and don't run it on sources.
The linker is complaining about missing the object code that comes from compiling moc output. That's because although moc has processed the source file, its output was not compiled into an object file.
For header files, the build system assumes that they are meant for inclusion in multiple translation units and won't violate the one definition rule. Thus moc output can include the header file, and get compiled as a stand-alone translation unit.
But, if you have any Q_OBJECT
macros within a .cpp
file, the moc output cannot be compiled in isolation: it won't have access to the declarations from your .cpp
file and thus can't compile! It also can't include your .cpp
file, since that would violate the one definition rule: two translation units - moc output and your .cpp
file - would define the same stuff.
Instead, you need to append moc's output to the end of the .cpp
file. E.g., if you have O_OBJECT
in main.cpp
, add #include "main.moc"
at the end of the file:
// main.cpp
#include <QtCore>
struct Object : QObject {
Q_OBJECT
};
int main() {
Object o;
qDebug() << o.metaObject()->className();
}
#include "main.moc"
// "main.moc" depends on the declaration of Object above!
The above is an SSCCE.
One could argue that perhaps qmake/cmake should set up the build so that moc output is automatically appended to the .cpp
file before it's sent to the compiler. So far, that feature is not implemented.