24 Stimmen

Das Hinzufügen von Klassendeklarationen in einer .cpp-Datei

Ist es möglich, Klassendeklaration und Implementierung in derselben .cpp-Datei zu haben?

Ich möchte einige Unittests mit Hilfe eines Mock-Objekts machen. Hier ist ein Beispiel meines Tests:

// Einige Include-Anweisungen entfernt

#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(...
...
...

Wie Sie sehen können, wird die Klasse ConnectionMockup nur von der Klasse TestConnection verwendet, und ich brauche sie nirgendwo anders. Also, wenn ich versuche, dieses Programm zu kompilieren, erhalte ich folgenden Fehler:

> testemcprogram.o: In Funktion  
> `ConnectionMockup':  
> /home/sasa/Desktop/QtPro/FocoKernel-build-desktop/../FocoKernel/testemcprogram.cpp:29:  
> undefinierter Verweis auf `Virtuelle Tabelle für  
> ConnectionMockup'  
> /home/sasa/Desktop/QtPro/FocoKernel-build-desktop/../FocoKernel/testemcprogram.cpp:29:  
> undefinierter Verweis auf `Virtuelle Tabelle für  
> ConnectionMockup' testemcprogram.o: In  
> Funktion `~ConnectionMockup':  
> /home/sasa/Desktop/QtPro/FocoKernel-build-desktop/../FocoKernel/testemcprogram.cpp:14:  
> undefinierter Verweis auf `Virtuelle Tabelle für  
> ConnectionMockup'

Ist es möglich, die Deklaration hier zu lassen, oder muss ich eine Header-Datei erstellen und die Deklaration in diese Datei verschieben?

EDIT: Da Herr Jerry Coffin (Danke, Herr Coffin) vorgeschlagen hat, dass möglicherweise einige virtuelle Funktionen nicht implementiert sind, werde ich hier die Deklaration von AbstractConnection einfügen, damit wir diese Möglichkeit überprüfen können:

#include 

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;

};

LÖSUNG: Dank @JCooper, @iammilind und @Jerry Coffin haben wir die Lösung. Nachdem der Destruktor aus AbstractConnection entfernt wurde (da er tatsächlich nichts tut) und Q_OBJECT aus ConnectionMockup entfernt wurde, funktioniert es.

0 Stimmen

Danke. Ich habe es im Text jetzt korrigiert.

0 Stimmen

@Sasa: Ich habe den ersten Satz bewusst nicht korrigiert.. Deklaration und Implementierung sind hier synonym. ;)

0 Stimmen

@0A0D: Tatsächlich ist es nicht. Eine Klassendeklaration sieht so aus: class myclass; Etwas wie: class myclass { /* ... */ }; ist eine Klassendefinition. Sie können dann Klassenmember separat von der Definition der Klasse selbst definieren.

21voto

Das Makro Q_OBJECT deklariert eine Reihe von Metaobjekt-Mitgliedsfunktionen. Das MOC-Build-Tool ist dafür verantwortlich, .h-Dateien zu parsen und diese Funktionsdeklarationen zu definieren. Beachten Sie, dass es keine .cpp-Dateien parsen. In Ihrem Fall konnte das vtable nicht gefunden werden, weil das MOC-Tool Ihre .cpp-Datei nicht analysiert hat. Die Lösung besteht darin, Ihre Klassendefinition in eine Headerdatei zu verschieben und die Headerdatei Ihrer .pro-Datei hinzuzufügen. Eine zweite Lösung - ein wenig "tricky" - ist, folgendes zu tun:

#include 
#include 

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;
}

Beachten Sie das `#include "myfile.moc"` unter der Klassendefinition.

Dies funktioniert, weil qmake das MOC-Tool für alle Dateien mit einer #include-Direktive aufruft. Somit wird MOC die .cpp-Datei parsen und die Metaobjektfunktionsdefinitionen generieren, was Ihren Linkerfehler lösen wird.

16voto

Jerry Coffin Punkte 452852

Ja, es ist vollkommen legitim und zulässig, eine Klasse und ihre Memberfunktionen in einer einzigen Datei zu definieren. Tatsächlich ist das aus Sicht des Compilers im Wesentlichen immer der Fall - Sie haben die Klassendefinition in einem Header und inkludieren diesen Header in der Quelldatei, in der Sie ihre Memberfunktionen implementieren.

Die Fehler, auf die Sie gestoßen sind, scheinen Linkerfehler zu sein, keine Compilerfehler. Was genau fehlt, ist aus dem, was Sie gepostet haben, nicht vollständig klar. Eine Möglichkeit ist, dass Ihre Basisklasse einige reine virtuelle Funktionen enthält, die Sie in der abgeleiteten Klasse nicht implementiert haben, aber ich bin überhaupt nicht sicher, ob das korrekt ist.

2 Stimmen

Ich glaube, ich habe die Ursache für den Fehler gefunden aber keine Lösung (außer das Verschieben der Deklaration in die Header-Datei). Ich habe hier einen ähnlichen Fehler gefunden. Ich vermute, es liegt daran, dass es ein Makro Q_OBJECT in meiner Klasse ConnectionMockup gibt und da ich keine Header-Datei habe, überspringt der Qt-Moc dieses Quelltext und generiert keine zusätzlichen Funktionen/Code/usw. :-?

2 Stimmen

@Sasa Du solltest nicht den Q_OBJECT-Makro verwenden müssen, für das, was du tust, da du in deinem Mockup keine Signale/Slots speziell verwendest. Hast du versucht, es zu entfernen?

0 Stimmen

@JCooper Vielen Dank! Ich habe gerade Q_OBJECT aus ConnectionMockup entfernt und die Zerstörung aus AbstractConnection (Danke an @iammilind) und jetzt funktioniert es. Aber ich verstehe nicht, wie das Auslösen eines Signals in test_send_message jetzt funktioniert, wenn kein Q_OBJECT vorhanden ist.

3voto

iammilind Punkte 64857

Wenn die Basisklasse eine virtuelle Funktion hat, die nicht rein ist, muss ihre Definition beim Kompilieren der endgültigen Binärdatei enthalten sein, da andernfalls ein Linkerfehler für vtable oder typeinfo auftritt. Schauen Sie sich das folgende Beispiel an:

// 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;
}

Jetzt wird das Komplilieren und Verlinken für Derived.cpp und Base.cpp gut funktionieren. Beide .cpp-Dateien können auch separat kompiliert werden, um Objektdateien zu erstellen und dann verlinkt zu werden.

Von Ihrer Frage aus gehe ich davon aus, dass Sie irgendwie die .cpp-/Objektdatei von Klasse AbstractConnection nicht anhängen, die immer noch eine nicht reine virtuelle Funktion enthält - ihren Destruktor. Wenn Sie diese Definition zusammen mit Ihrem ConnectionMockup kompilieren, sollte der Linkerfehler nicht auftreten. Entweder können Sie die Datei mit dem Destruktor-Körper kompilieren oder den Destruktor-Körper direkt in der Klassendefinition definieren.

0 Stimmen

+1 für die Erwähnung des Destruktors. Nachdem ich ihn entfernt habe (da er tatsächlich nichts tut), habe ich weniger Fehler bekommen. Danke.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X