Помещение объявления класса в файл .cpp
Возможно ли иметь объявление класса и реализацию в том же файле .cpp?
Я хочу выполнить некоторые модульные тесты с помощью mock-объекта. Вот пример моего теста:
// Some includes removed
#include "abstractconnection.h"
class ConnectionMockup : public AbstractConnection
{
Q_OBJECT
public:
explicit ConnectionMockup(QObject *parent = 0);
bool isReady() const;
void sendMessage(const QString &message);
void test_send_message(const QString &message);
bool ready;
QStringList messages;
};
ConnectionMockup::ConnectionMockup(QObject *parent)
: AbstractConnection(parent)
{
ready = true;
}
bool ConnectionMockup::isReady() const
{
return ready;
}
void ConnectionMockup::sendMessage(const QString &message)
{
messages.append(message);
}
void ConnectionMockup::test_send_message(const QString &message)
{
emit messageRecieved(message);
}
TestEmcProgram::TestEmcProgram(QObject *parent) :
QObject(parent)
{
}
void TestEmcProgram::open()
{
ConnectionMockup mockup;
EmcProgram program(&mockup);
QCOMPARE(...
...
...
Как вы можете видеть, класс ConnectionMockup используется только классом TestConnection, и мне он больше не нужен. Поэтому, когда я пытаюсь скомпилировать эту программу, я получаю следующую ошибку:
> testemcprogram.o: In function
> `ConnectionMockup':
> /home/sasa/Desktop/QtPro/FocoKernel-build-desktop/../FocoKernel/testemcprogram.cpp:29:
> undefined reference to `vtable for
> ConnectionMockup'
> /home/sasa/Desktop/QtPro/FocoKernel-build-desktop/../FocoKernel/testemcprogram.cpp:29:
> undefined reference to `vtable for
> ConnectionMockup' testemcprogram.o: In
> function `~ConnectionMockup':
> /home/sasa/Desktop/QtPro/FocoKernel-build-desktop/../FocoKernel/testemcprogram.cpp:14:
> undefined reference to `vtable for
> ConnectionMockup'
Можно ли оставить объявление здесь, или я должен создать файл заголовка и перенести объявление в этот файл?
EDIT:. Поскольку г-н Джерри Коффин (спасибо, г-н Коффин) предложил, чтобы у меня не было каких-либо виртуальных функций, я приведу здесь объявление AbstractConnection, чтобы мы могли рассмотреть эту возможность:
#include <QObject>
class AbstractConnection : public QObject
{
Q_OBJECT
public:
explicit AbstractConnection(QObject *parent = 0);
virtual ~AbstractConnection();
virtual bool isReady() const = 0;
signals:
void messageRecieved(const QString &message);
public slots:
virtual void sendMessage(const QString &message) = 0;
};
РЕШЕНИЕ: Благодаря @JCooper, @iammilind и @Jerry Coffin у нас есть решение. После удаления деструктора из AbstractConnection (поскольку он фактически ничего не делает) и удаления Q_OBJECT из ConnectionMockup он работает.
Ответы
Ответ 1
Да, это полностью законно и допустимо определить класс и его функции-члены в одном файле. На самом деле, с точки зрения компилятора, который по существу всегда имеет место, вы имеете определение класса в заголовке и включаете этот заголовок в исходный файл, где вы реализуете его функции-члены.
Ошибки, с которыми вы столкнулись, выглядят как ошибки компоновщика, а не ошибки компилятора. То, что пропало, не совсем ясно из того, что вы опубликовали. Одна из возможностей заключается в том, что ваш базовый класс имеет некоторые чистые виртуальные машины, которые вы не смогли реализовать в производном классе, но я не совсем уверен, что это правильно.
Ответ 2
Макрос Q_OBJECT
объявляет набор функций-членов метаобъекта. Инструмент сборки MOC отвечает за разбор .h файлов и определение этих объявлений функций. Обратите внимание, что он не анализирует файлы .cpp. В вашем случае vtable
не может быть найден, потому что инструмент MOC не проанализировал ваш .cpp файл. Решение состоит в том, чтобы переместить определение класса внутри файла заголовка и добавить заголовок в ваш .pro файл. Второе решение - бит "взломанный" - это сделать следующее:
#include <QObject>
#include <QtDebug>
class Counter : public QObject
{
Q_OBJECT
public:
Counter() { value = 0; }
int getValue() const { qDebug() << "getValue()"; return value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int value;
};
#include "main.moc"
void Counter::setValue(int value)
{
qDebug() << "setValue()";
if (this->value != value) {
this->value = value;
emit valueChanged(value);
}
}
int main()
{
Counter a, b;
QObject::connect(
&a, &Counter::valueChanged,
&b, &Counter::setValue);
a.setValue(12);
b.setValue(48);
return 0;
}
Обратите внимание на `#include "myfile.moc" в определении класса.
Это работает, потому что qmake будет вызывать инструмент MOC для любых файлов с директивой #include. Таким образом, MOC будет анализировать файл .cpp и генерировать определения функции метаобъекта, устраняя ошибку компоновщика.
Ответ 3
Если у базового класса есть какая-либо функция virtual
, которая не является чистой, это определение должно быть включено при компиляции окончательного двоичного кода, в противном случае оно дает ошибку компоновщика для vtable
или typeinfo
. Пример ниже:
// Base.h
struct Base {
virtual void fun() = 0;
virtual ~Base();
};
// Base.cpp
#include"Base.h"
Base::~Base () {}
// Derived.cpp
#include"Base.h"
struct Derived : Base {
void fun () {}
};
int main () {
Derived d;
}
Теперь компиляция для Derived.cpp и Base.cpp будет работать нормально. Оба .cpp файла также могут быть скомпилированы отдельно для создания объектных файлов, а затем связаны между собой.
Из вашего вопроса, я считаю, что вы не каким-то образом прикрепляете файл .cpp/object от class AbstractConnection
, который по-прежнему содержит одну нечистую виртуальную функцию - ее destructor
. Если вы скомпилируете это определение вместе с вашим ConnectionMockup
, тогда ошибка компоновщика не должна появляться. Либо вы можете скомпилировать файл, включая тело деструктора, либо определить тело деструктора в самом определении класса.