Помещение объявления класса в файл .cpp - PullRequest
19 голосов
/ 14 апреля 2011

Можно ли иметь объявление и реализацию класса в одном и том же файле .cpp?

Я хочу провести модульное тестирование с помощью фиктивного объекта.Вот пример моего теста:

// 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'

Можно ли оставить здесь объявление или мне нужно создать заголовочный файл и переместить объявление в этот файл?

РЕДАКТИРОВАТЬ: Поскольку г-н Джерри Коффин (спасибо, г-н Коффин) предположил, что у меня могут не быть реализованы некоторые виртуальные функции, я помещу здесь объявление 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 он работает.

Ответы [ 3 ]

17 голосов
/ 18 мая 2014

Макрос 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 и сгенерирует определения функций мета-объекта, устраняя ошибку компоновщика.

14 голосов
/ 14 апреля 2011

Да, вполне допустимо и допустимо определять класс и его функции-члены в одном файле. Фактически, с точки зрения компилятора, это по сути всегда так - у вас есть определение класса в заголовке, и вы включаете этот заголовок в исходный файл, где вы реализуете его функции-члены.

Ошибки, с которыми вы столкнулись, выглядят как ошибки компоновщика, а не ошибки компилятора. Точно то, что отсутствует, не совсем понятно из того, что вы опубликовали. Одна возможность состоит в том, что в вашем базовом классе есть несколько чистых виртуальных машин, которые вы не смогли реализовать в производном классе, но я совсем не уверен, что это правильно.

3 голосов
/ 14 апреля 2011

Если в базовом классе есть какая-либо функция 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 / class AbstractConnection, который все еще содержит одну не чистую виртуальную функцию - ее destructor. Если вы скомпилируете это определение вместе с вашим ConnectionMockup, ошибка компоновщика не должна появиться. Либо вы можете скомпилировать файл, включающий тело деструктора, либо определить тело деструктора в самом определении класса.

...