同じ.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'
ここに宣言を残すことは可能ですか、それともヘッダーファイルを作成して宣言をそのファイルに移動する必要がありますか?
編集: Jerry Coffin氏(ありがとうCoffin氏)は、いくつかの仮想関数を実装していない可能性があることを示唆しているため、ここで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からデストラクタを削除し(実際には何もしないため)、ConnectionMockupからQ_OBJECTを削除すると機能します。
はい、クラスとそのメンバー関数を1つのファイルで定義することは完全に合法であり、許容されます。実際、本質的に常にそうであるコンパイラーの観点からは、ヘッダーにクラス定義があり、メンバー関数を実装するソースファイルにそのヘッダーを含めます。
発生したエラーは、コンパイラエラーではなく、リンカエラーのように見えます。何が欠けているかは、あなたが投稿したものから完全に明確ではありません。 1つの可能性として、派生クラスに実装できなかったいくつかの純粋な仮想が基本クラスにありますが、私はそうではありませんまったく正しいと確信しています。
Q_OBJECT
マクロは、メタオブジェクトメンバー関数のセットを宣言します。 MOCビルドツールは、.hファイルの解析とこれらの関数宣言の定義を担当します。 .cppファイルは解析されないことに注意してください。あなたの場合、MOCツールが.cppファイルを解析しなかったため、vtable
が見つかりませんでした。解決策は、クラス定義をヘッダーファイル内に移動し、ヘッダーを.proファイルに追加することです。 2番目の解決策-少し「ハッキー」-は、次のことを行うことです。
#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が#includeディレクティブを含むすべてのファイルでMOCツールを呼び出すため、機能します。したがって、MOCは.cppファイルを解析してメタオブジェクト関数定義を生成し、リンカーエラーを解決します。
Baseクラスに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ファイルは、オブジェクトファイルを作成するために個別にコンパイルしてからリンクすることもできます。
あなたの質問から、私が感じていることは、class AbstractConnection
の.cpp/objectファイルを何らかの方法で添付していないということです。このファイルには、純粋でない仮想関数が1つだけ含まれています-そのdestructor
。 ConnectionMockup
とともにその定義をコンパイルすると、リンカーエラーは表示されなくなります。デストラクタ本体を含むファイルをコンパイルするか、クラス定義自体でデストラクタ本体を定義することができます。