Qt をはじめよう! 第11回: QObject の派生クラスを作成しよう

Published Wednesday July 7th, 2010
25 Comments on Qt をはじめよう! 第11回: QObject の派生クラスを作成しよう
Posted in Qt をはじめよう! | Tags: , ,

前回はシグナルとスロットと、その使い方について解説しました。”次回は独自のシグナルとスロットを作成してみましょう。”と書きましたが、その前に、シグナルとスロットの作成のために必要な QObject の派生クラスの作成について解説することにします。

QObject の派生クラスの作成

前回まで使用してきたサンプルに QObject の派生クラス Object を追加しましょう。

前回までに作成したプロジェクトを開いた状態で ファイル(F) -> ファイル/プロジェクトの新規作成(N)… を選択してください。

Qt Creator 2.0 では新規作成ダイアログがより使いやすくなりました。

「ファイルとクラス」の中から「C++」を選択し、右上のリストの「C++ クラス」を選択し、「選択(C)…」ボタンをクリックします。

C++ クラスウィザード

クラス名: Object

基底クラス:QObject

型情報:QObject を継承

と設定し、「次へ(N)>」をクリックします。

object.h と object.cpp が作成され、プロジェクト example.pro に追加されます。

「完了(F)」 をクリックし、ウィザードを終了してください。

example.pro

Qt のアプリケーション開発ではそのプロジェクトの情報を <プロジェクト名>.pro というプロジェクトファイルで管理します。プロジェクトファイルにはそのプロジェクトに含まれるソースコードとヘッダファイルのリストや、その他のファイルの情報、様々な設定などが含まれます。Qt Creator でプロジェクトを作成した際にはプロジェクトファイルが自動で生成され、ファイルを追加したり削除したりした際にもプロジェクトファイルは更新されます。Qt Creator を使用しない場合には手動で管理していただく必要があります。

Qt Creator でプロジェクトツリーの中から example.pro を開いて、内容を確認してみましょう。

QT       += core gui    # [1]
TARGET = example        # [2]
TEMPLATE = app          # [3]
SOURCES += main.cpp 
    example.cpp 
    object.cpp          # [4]
HEADERS  += example.h 
    object.h            # [5]

[1] 使用する Qt のモジュールを指定します

[2] 生成する実行バイナリの名前を指定します

[3] Qt のプロジェクトのテンプレートを設定します

[4] ソースコードの一覧

[5] ヘッダファイルの一覧

Qt ではこの(プラットフォームに依存しない形式の)プロジェクトファイルを、qmake という名前のツールを使用して、各プラットフォームのビルド環境に応じた形式(Makefile等)に変換しビルドをできるようにします。Qt Creator では Qt のプロジェクトを新規作成した際やプロジェクトファイルを変更した後の、ビルドの一番最初に qmake が実行されています。

ここでは qmake の使い方やプロジェクトファイルの形式に付いての詳細な解説は省略します。詳細は [qt “qmake Manual” l=qmake-manual] をご覧ください。

object.h

Qt Creator で作成した Object クラスのヘッダファイルです。

#ifndef OBJECT_H
#define OBJECT_H

#include <QObject> // [1]

class Object : public QObject // [2]
{
    Q_OBJECT    // [3]
public:
    explicit Object(QObject *parent = 0); // [4]

signals:      // [5]

public slots: // [6]

};

#endif // OBJECT_H

[1] QObject クラスのヘッダファイルをインクルードします

[2] QObject クラスのサブクラス Object クラスを定義します

[3] [qt “Q_OBJECT” l=qobject m=#Q_OBJECT] はマクロで、QObject (とその派生クラス)を継承したクラスを作成する際に、定義の一番初めに記述します。第7回 Qt のオブジェクトモデルを理解しよう で解説した Qt のオブジェクトモデルの様々な機能の実現に必要なコードに置き換えられます。QObject の派生クラスを作成する際には必ず Q_OBJECT マクロを記述するよう心がけてください。

[4] デフォルトのコンストラクタの定義です。Qt のオブジェクトではこのように親オブジェクト(ウィジェットの場合は親ウィジェット)を指定できるようにしておくのが一般的です。

[5] シグナル宣言用のスコープです。シグナルはこのスコープに追加することになります。このスコープは、後述の moc がシグナルの情報を取得するためのキーワードとなります。この “signals” は実際にはマクロとして定義されていて、コンパイル時に “protected” に置換されます。独自のシグナルを持たない場合にはこのスコープの宣言は必要ありません。

[6] public なスロット宣言用のスコープです。スロットの場合はアクセスレベルに応じて public slots:、protected slots:、private slots: というスコープが使い分けられます。”slots” も “signals” 同様、moc に対するキーワードの目的で使用され、コンパイル時にはマクロの展開によって “”(空文字列) に置換されます。独自のスロットを持たない場合には各スロット用のスコープの宣言は必要ありません。

object.cpp

Object クラスの実装ファイルです。

#include "object.h"

Object::Object(QObject *parent) :
    QObject(parent)
{

}

ヘッダファイルをインクルードし、Object のコンストラクタで基底クラスのコンストラクタを呼び出しています。

以上が、QObject の派生クラスを作成する際の典型的なヘッダファイル/ソースファイルになります。

ビルド

それでは Qt Creator でビルドをしてください。Alt+4(Mac では Command+4) で表示されるコンパイル出力を眺めてみると、g++ による cpp ファイルのコンパイルに混じって、以下のように moc コマンドが実行されている事を確認してみてください。

/home/tasuku/qtsdk-2010.04/qt/bin/moc -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/home/tasuku/qtsdk-2010.04/qt/mkspecs/linux-g++ -I../example -I/home/tasuku/qtsdk-2010.04/qt/include/QtCore -I/home/tasuku/qtsdk-2010.04/qt/include/QtGui -I/home/tasuku/qtsdk-2010.04/qt/include -I. -I../example -I. ../example/object.h -o moc_object.cpp

今回はクラスを追加しただけで、それを使用するコードは書いていませんので、実行結果は前回と変わりません。

メタオブジェクトコンパイラ(moc)

上記で実行されている moc はメタオブジェクトコンパイラ(Meta-Object Compiler)と呼ばれ、ヘッダファイルやソースファイルを解析し、Qt のオブジェクトシステムに必要なメタオブジェクト情報を生成する為の Qt のツールです。QObject の派生クラスのクラス情報や、追加したシグナルやスロット、プロパティなどの情報が moc が自動生成するファイル moc_<ソースファイル名(.cpp)> に含まれます。qmake が自動でヘッダファイルを捜索し、Q_OBJECT マクロが見つかった場合にはそのファイルに対して moc を実行するルールを作成します。生成されたソースファイルはコンパイルされ、バイナリの一部として含まれます。

ヘッダファイルではなく、ソースファイル(.cpp)内で QObject の派生クラスを定義する場合には、そのままでは qmake による自動的なルールの作成は行われませんので注意が必要です。この場合、そのソースファイルの中で、クラスの定義以降の場所に

#include "<ソースファイル名(拡張子なし)>.moc"

という行を追加する必要があり、これにより qmake はこの moc ファイルを生成するルールを作成するようになります。この行を追加した後には一度手動で qmake を実行する必要があります。Qt Creator では ビルド(B) -> qmake 実行 を実行してください。

例えば、main.cpp ファイル内に QObject の派生クラスを定義する場合には、

#include "main.moc"

という行の追加が必要です。なお、慣例的にこの行はファイルの一番最後に書かれることが多いようです。

この行がない場合にはコンパイル時に以下のようなエラーが出るでしょう。

undefined reference to vtable for <クラス名>

moc についての詳細はドキュメント [qt “Using the Meta-Object Compiler (moc)” l=moc] を参照してください。

おわりに

今回は QObject の派生クラスの作成をするとともに、Qt のプロジェクトファイルと、ビルド時に使用するツール qmake と moc について簡単に解説しました。次回は独自のシグナルとスロットを作成し、使用してみましょう。

Do you like this? Share it
Share on LinkedInGoogle+Share on FacebookTweet about this on Twitter

Posted in Qt をはじめよう! | Tags: , ,

25 comments

[…] This post was mentioned on Twitter by 朝木卓見 and Qt Japan. Qt Japan said: Qt Labs JP – QObject の派生クラスを作成しよう http://bit.ly/9bX7Ob […]

[…] This post was mentioned on Twitter by 朝木卓見 and Qt Japan. Qt Japan said: Qt Labs JP – QObject の派生クラスを作成しよう http://bit.ly/9bX7Ob […]

ahigerd says:

(笑)こんなポストは英語でどこにあるかな。僕はいつも#qtに説いてるね。

yjw says:

QObjectの派生クラスについては、コピーコンストラクタと代入演算子を持っていないことを書かれると良いと思います。
一度これでハマってしまったことがあるので・・・。

yjw says:

QObjectの派生クラスについては、コピーコンストラクタと代入演算子を持っていないことを書かれると良いと思います。
一度これでハマってしまったことがあるので・・・。

鈴木 佑 says:

@ahigerd これと同じような内容はドキュメントの Signals and Slots にあります。

鈴木 佑 says:

@ahigerd これと同じような内容はドキュメントの Signals and Slots にあります。

鈴木 佑 says:

@yjwさん 非常に有益なコメントありがとうございます。

コピーや代入ができないことを Qt のオブジェクトモデルを理解しよう の記事に書いておけば良かったですね。もしくは今後の記事で取り上げさせていただくかもしれません。

より正確に記述すると、Qt に含まれる QObject の派生クラスでは Q_DISABLE_COPY というマクロを使用してコピーコンストラクタと代入演算子を使用できないようにしています。詳細はドキュメント Qt Object Model の最後の方をお読みください。
Qt のプログラミングではオブジェクトのコピーや代入は避け、ポインタや参照を使用するようにしてください。

今回作成した Object クラスの場合はこのマクロを使用していないので、C++ の言語仕様により自動的にコピーコンストラクタと代入演算子が作成されます。代入は基底クラス(QObject)の代入演算子が使用できないためコンパイル時にエラーが出ます。コピーについてはエラーにはなりませんね。ということで、Object クラスも Q_DISABLE_COPY マクロを使用すべきです。

鈴木 佑 says:

@yjwさん 非常に有益なコメントありがとうございます。

コピーや代入ができないことを Qt のオブジェクトモデルを理解しよう の記事に書いておけば良かったですね。もしくは今後の記事で取り上げさせていただくかもしれません。

より正確に記述すると、Qt に含まれる QObject の派生クラスでは Q_DISABLE_COPY というマクロを使用してコピーコンストラクタと代入演算子を使用できないようにしています。詳細はドキュメント Qt Object Model の最後の方をお読みください。
Qt のプログラミングではオブジェクトのコピーや代入は避け、ポインタや参照を使用するようにしてください。

今回作成した Object クラスの場合はこのマクロを使用していないので、C++ の言語仕様により自動的にコピーコンストラクタと代入演算子が作成されます。代入は基底クラス(QObject)の代入演算子が使用できないためコンパイル時にエラーが出ます。コピーについてはエラーにはなりませんね。ということで、Object クラスも Q_DISABLE_COPY マクロを使用すべきです。

[…] This post was mentioned on Twitter by Yuji Watanabe. Yuji Watanabe said: 早々の対応ありがとうございます! RT @tasukusuzuki: @yjw1974 Qt Labs Japan へのコメントありがとうございました。ご指摘の点は Qt を始め […]

[…] This post was mentioned on Twitter by Yuji Watanabe. Yuji Watanabe said: 早々の対応ありがとうございます! RT @tasukusuzuki: @yjw1974 Qt Labs Japan へのコメントありがとうございました。ご指摘の点は Qt を始め […]

kenji says:

コピーでエラーになるのではないですか。

鈴木 佑 says:

kenji さん、コピーも QObject のコピーコンストラクタが private なのでエラーになりますね。ご指摘ありがとうございました。

Dr_Radialist says:

Windows7にQt SDK by Nokia V2010.04をインストールしました
デモのanimatedtitlesをQt Creatorでビルドすると、
main.moc: No such files or directoryというエラーが出てしまいbuildできません。
build -> qmake を実行しても一緒でした。ご指導下さい。

鈴木 佑 says:

Dr_Radialist さん、ご指摘ありがとうございました。

こちらで試したところ同様の現象が再現いたしました。原因や対策が分かり次第改めてお知らせいたします。

鈴木 佑 says:

Dr_Radialist さん、Qt SDK 2010.04 のインストール時にコピーされている以下のファイルを削除して、再度 qmake を行なってビルドが正常に終了するか確認していただけますか?
c:Qt2010.04qtexmaplesanimationanimatedtilestmpmocrelease_sharedmain.moc

nakasya says:

鈴木さんへ

的はずれな質問かもしれませんが、
私はコピーの弊害は理解しているつもりです。
>Qt のプログラミングではオブジェクトのコピーや代入は避け、ポインタや参照を使用するようにしてください。
普通はそのとおりだと思われます。 しかし、
私はマルチスレッドにおいてスレッドセーフにするため、スレッド間通信時にオブジェクトのコピーをよく使います。 Qtのスタイルではどうされているのでしょうか?

鈴木 佑 says:

nakasya さん、コメントありがとうございます。

Qt でスレッドを使用する場合、基本的にはオブジェクトをスレッドセーフに設計・実装し、同じオブジェクトのインスタンスに複数のスレッドからアクセスする形になります。また、シグナル・スロットはスレッドをまたいでも使用できるように設計されています。
具体的にはどういったケースでオブジェクトのコピーを想定されていますか?

スレッドを使用する際には、ドキュメント「Threads and QObjects」や、Qt Developer Days 2008 のマルチスレッドに関するセッション「Enhancing your Qt Application with Multiple Threads」の内容が参考になると思いますので、是非ご覧ください。

nakasya says:

鈴木さんへ

枡田です。
アドバイスありがとうございます。紹介されたページは読んでいなかったので参考になります。
私がスレッド間の通信で使うのはテンポラリな領域としてデータの受け渡しに使っています。
この場合だと多く同じクラスのスレッドが存在するときや、共有データでないので同期オブジェクトの管理が複雑にならないメリットがあるのではないかと考えています。

QThreadについてはスレッド間でconnect関数とそのQt::ConnectionTypeの動きで把握しきれていない状態で、なれるのにもうすこし試す必要があると思っています。
ついでですが、connectの呼ぶ場所がメインスレッドやサブスレッドでは、私が期待した動きにならので悩んでいます。

nakasya says:

鈴木さんへ

枡田です。
アドバイスありがとうございます。紹介されたページは読んでいなかったので参考になります。
私がスレッド間の通信で使うのはテンポラリな領域としてデータの受け渡しに使っています。
この場合だと多く同じクラスのスレッドが存在するときや、共有データでないので同期オブジェクトの管理が複雑にならないメリットがあるのではないかと考えています。

QThreadについてはスレッド間でconnect関数とそのQt::ConnectionTypeの動きで把握しきれていない状態で、なれるのにもうすこし試す必要があると思っています。
ついでですが、connectの呼ぶ場所がメインスレッドやサブスレッドでは、私が期待した動きにならので悩んでいます。

srd says:

こんにちは。Qtを学び始めたばかりの者です。
”Qt をはじめよう!”の 第1回から順に追ってみていますが、
すごくわかりやすくて良いですね。

Dr_Radialist 2010/07/21 09:02と同じ現象が発生しておりましたが、
鈴木 佑 2010/07/26 22:47に書かれている方法でビルド、実行できました。

ただの報告ですが、ここを閲覧している方の参考になれば…。

w-wolf says:

初めまして。 Qt初心者です。

私も早速サンプルコードを動かしてみたら「Dr_Radialist 2010/07/21 09:02」と
同じ現象になりましたが、この後の鈴木さんの解決方法を実行したら問題が解決
しました。

初めて動かして、いきなりこのエラーに遭遇してしまったので困っていました。
解決方法を教えていただきありがとうございました。

因みに、私の環境はWindows7でQt SDK 2010.05でした。

でも、どうしてインストールパッケージに含まれている「main.moc」を
削除するとエラーが解消されるのでしょうか?

まだまだ初心者ですが、少しずつQt勉強していきたいと思います。

鈴木 佑 says:

w-wolf さん、初めまして。

main.moc を削除するとエラーが解消される理由ですが、qmake が生成する Makefile(.Release) の依存関係とコンパイル時のインクルードパスのミスマッチによるものです。

qmake は main.cpp の中に #include “main.moc” という行を発見し、moc コマンドにより main.moc をビルドディレクトリ(animated-tiles-build)の tmp/release_shared/ 以下に生成するルールを作成します。

main.cpp はここに生成される main.moc に依存する(ためこの main.moc ファイルも生成される)はずなのですが、今回の場合はソースディレクトリ以下に main.moc というファイルがあるため、間違ってこちらに依存してしまっています。しかし、コンパイル時のインクルードパスにはビルドディレクトリ以下の tmp/release_shared/ は含まれていますが、ソースディレクトリのパスは含まれていないため、main.moc が見つからず、エラーになってしまっています。

問題が発生する状態で、ビルドディレクトリの Makefile.Debug と Makefile.Release で “main.moc” を検索していただいて、依存関係とビルド時のインクルードパスの設定のあたりを確認していただければと思います。

なお、今回の問題は パッケージ作成の問題 ということで 最新の Qt SDK ではすでに解消されています。

鈴木 佑 says:

w-wolf さん、初めまして。

main.moc を削除するとエラーが解消される理由ですが、qmake が生成する Makefile(.Release) の依存関係とコンパイル時のインクルードパスのミスマッチによるものです。

qmake は main.cpp の中に #include “main.moc” という行を発見し、moc コマンドにより main.moc をビルドディレクトリ(animated-tiles-build)の tmp/release_shared/ 以下に生成するルールを作成します。

main.cpp はここに生成される main.moc に依存する(ためこの main.moc ファイルも生成される)はずなのですが、今回の場合はソースディレクトリ以下に main.moc というファイルがあるため、間違ってこちらに依存してしまっています。しかし、コンパイル時のインクルードパスにはビルドディレクトリ以下の tmp/release_shared/ は含まれていますが、ソースディレクトリのパスは含まれていないため、main.moc が見つからず、エラーになってしまっています。

問題が発生する状態で、ビルドディレクトリの Makefile.Debug と Makefile.Release で “main.moc” を検索していただいて、依存関係とビルド時のインクルードパスの設定のあたりを確認していただければと思います。

なお、今回の問題は パッケージ作成の問題 ということで 最新の Qt SDK ではすでに解消されています。

鈴木 佑 says:

w-wolf さん、初めまして。

main.moc を削除するとエラーが解消される理由ですが、qmake が生成する Makefile(.Release) の依存関係とコンパイル時のインクルードパスのミスマッチによるものです。

qmake は main.cpp の中に #include “main.moc” という行を発見し、moc コマンドにより main.moc をビルドディレクトリ(animated-tiles-build)の tmp/release_shared/ 以下に生成するルールを作成します。

main.cpp はここに生成される main.moc に依存する(ためこの main.moc ファイルも生成される)はずなのですが、今回の場合はソースディレクトリ以下に main.moc というファイルがあるため、間違ってこちらに依存してしまっています。しかし、コンパイル時のインクルードパスにはビルドディレクトリ以下の tmp/release_shared/ は含まれていますが、ソースディレクトリのパスは含まれていないため、main.moc が見つからず、エラーになってしまっています。

問題が発生する状態で、ビルドディレクトリの Makefile.Debug と Makefile.Release で “main.moc” を検索していただいて、依存関係とビルド時のインクルードパスの設定のあたりを確認していただければと思います。

なお、今回の問題は パッケージ作成の問題 ということで 最新の Qt SDK ではすでに解消されています。

Leave a Reply

Your email address will not be published.

Get started today with Qt Download now