¿Por qué mi proyecto no vincula si hay macros Q_Object en archivos .cpp?
Pregunta
Este código compila, enlaza y funciona según lo previsto:
#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();
}
Pero cuando pongo la definición de clase en .cpp
Archivo En lugar de archivos de encabezado, obtengo errores de enlace que indican que vtable
no se definió correctamente.
#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'
Este parece ser exactamente el mismo código. ¿Por qué vincula cuando el código está en encabezados y no vincula de otra manera?
Solución
QT usa moc
Herramienta para manejar las extensiones de C ++ que requerían, por ejemplo, para el mecanismo de lotes de señales. Esta herramienta procesa todos los archivos de encabezado (!) En el proyecto y genera nuevos archivos de origen que contienen código de meta-objeto para aquellas clases que contienen Q_OBJECT
macro.
Cuando tienes tu clase definida en .cpp
archivo en lugar de .h
expediente moc
no lo puede procesar correctamente.
Mira esto Este artículo Para obtener más información sobre el compilador de meta-objeto QT.
Otros consejos
Porque QT ejecuta MOC en archivos de encabezado y no lo ejecute en fuentes.
El enlazador se queja de perder el código de objeto que proviene de compilar la salida MOC. Esto se debe a que aunque MOC ha procesado el archivo fuente, su salida no se compiló en un archivo de objeto.
Para los archivos de encabezado, el sistema de compilación supone que están destinados a su inclusión en múltiples unidades de traducción y no violarán el regla de definición. Por lo tanto, la salida MOC puede incluir el archivo de encabezado y ser compilado como una unidad de traducción independiente.
Pero, si tienes alguna Q_OBJECT
macros dentro de un .cpp
Archivo, la salida MOC no se puede compilar de forma aislada: no tendrá acceso a las declaraciones de su .cpp
Archivo y, por lo tanto, no se puede compilar! Tampoco puede incluir tu .cpp
archivo, ya que eso violaría el regla de definición: Dos unidades de traducción: salida MOC y tu .cpp
Archivo: definiría las mismas cosas.
En su lugar, debe agregar la salida de MOC al final del .cpp
expediente. Por ejemplo, si tienes O_OBJECT
en main.cpp
, agregar #include "main.moc"
Al final del archivo:
// 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!
Lo anterior es un SSCCE.
Se podría argumentar que quizás Qmake/CMake debería configurar la compilación para que la salida MOC se agrega automáticamente al .cpp
archivo antes de que se envíe al compilador. Hasta ahora, esa característica no se implementa.