Qt Tutorial ボイラープレートの各ファイルを読み解いていく
前回の記事が長いので2つに分割しました。
(注意)
今日の記事の内容は、今の段階でこれを理解するっていうのは超難しいし、私がQtの基礎の、さらに地下奥深くを掘り進めていくのは、学習期間の中ではどちらかというと後の方です。それを最初から言われても訳が分からないと思います。なので、わかるところだけを抑えていってください。学習を続けて、また戻ってきたりして、振り返る場所だと思います。
CMakeLists.txt
#CMakeのバージョンは最低でも3.16であることを要求する 最初に書くルール
cmake_minimum_required(VERSION 3.16)
#プロジェクトは、FirstProject バージョンは0.1 言語はC++
#project関数については、https://cmake.org/cmake/help/latest/command/project.html#command:project
#また、後ほど追加予定
project(FirstProject VERSION 0.1 LANGUAGES CXX)
https://cmake.org/cmake/help/latest/manual/cmake-qt.7.html#autouic
set(CMAKE_AUTOUIC ON)
#https://cmake.org/cmake/help/latest/manual/cmake-qt.7.html#automoc
set(CMAKE_AUTOMOC ON)
https://cmake.org/cmake/help/latest/manual/cmake-qt.7.html#autorcc
set(CMAKE_AUTORCC ON)
#C++ 標準 バージョン 17
set(CMAKE_CXX_STANDARD 17)
#https://cmake.org/cmake/help/latest/prop_tgt/CXX_STANDARD_REQUIRED.html#prop_tgt:CXX_STANDARD_REQUIRED
#ここがONだと、上記の17であることが要求(強制)される。17でなければ多分エラーが起きる
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#Qtというパッケージ names引数にQt6 Qt5が入る どちらかが要求される コンポ―ネントとしては Widgetsを指定
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
#上記とほとんど同じ意味。QT_VERSION_MAJORには、上で発見されたQtのバージョン数が入る
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
#プロジェクトのソースとして、4つのファイルが指定される.ファイルをプロジェクトに追加するごとに、これがふえる。
set(PROJECT_SOURCES
main.cpp
MainWindow.cpp
MainWindow.h
MainWindow.ui
)
#とはいうものの、実際はこちら側に追加される(記述によれば、Qt6以上のバージョンの場合)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(FirstProject
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
#アンドロイド用の設定
# Define target properties for Android with Qt 6 as:
# set_property(TARGET FirstProject APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(FirstProject SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(FirstProject
${PROJECT_SOURCES}
)
endif()
endif()
#find_packageで見つけたファイルをリンクする PRIVATEはPUBLICでもいいが、PRIVATE推奨
target_link_libraries(FirstProject PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
# MAC IOSの設定
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.FirstProject)
endif()
set_target_properties(FirstProject PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
include(GNUInstallDirs)
install(TARGETS FirstProject
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(FirstProject)
endif()CMakeLists.txtの詳細
このCMakeLists.txt を見ると、一体何なんだ?って最初は思われるかもしれません。これは、ビルド構成を示すファイルです。じゃあ、ビルド構成ってなんだ?ということになりますが、それはプログラムを組み立てるための指示書みたいなものです。
①パッケージ名はFirst Project バージョンは0.1 言語としてはC++
project(FirstProject VERSION 0.1 LANGUAGES CXX)
②Qt用の特殊コンパイラであるMOC, UIC, RCCの機能をONにする
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)③C++は17を使い、強制する(REQUIRED)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
④ライブラリはQtパッケージを見つけること。Qt6, Qt5という名前。結局どちらも検索する。これは絶対そうすること(REQUIRED)。見つけた方のうち、高いバージョンの方が優先されて、そのWidgetsコンポーネントも見つける。
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)⑤
set関数は、PROJECT_SOURCESという変数に、それ以降のファイルを加えます。御覧のように、自分達がプロジェクトで利用するファイルを指定しています。ヘッダファイルとソースファイル、そして、デザインファイル(ui)が指定されていますね。
set(PROJECT_SOURCES
main.cpp
MainWindow.cpp
MainWindow.h
MainWindow.ui
)その後、同じようにset関数が続いて、MANUAL_FINALIZATIONという意味不明な呪文が置いてあります。これは直訳すると、手動で最後にすると言う意味です。ファイルの最後に、qt_finalize_executable(FirstProject)と書かれていることと関わっています。簡単に言えば、ターゲットの自動最終処理をここでは行わないで、後にスキップする、という指示です。自動でお任せするのではなく、自分で命令したときに実行する、という意味になります。裏を返せば、Qt6以前までは、ここで自動で行われていた、ということになるようです。
#Qt6か、それよりも上のバージョンであるならば・・・
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(FirstProject
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)⑥アンドロイド用の記述
#アンドロイド用の設定
# Define target properties for Android with Qt 6 as:
# set_property(TARGET FirstProject APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(FirstProject SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(FirstProject
${PROJECT_SOURCES}
)
endif()
endif()⑦
target_link_librariesは、find_package関数で見つけたプロジェクトと、Qtのライブラリをリンクするように命令する関数です。
target_link_libraries(FirstProject PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)⑧Mac OS IOS用の設定
私は今のところMacにはあまり詳しくないので割愛します。
# MAC IOSの設定
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.FirstProject)
endif()
set_target_properties(FirstProject PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)⑧include(GNUInstallDirs)っていうのは、例えばプロジェクトを作ると、binファイルやlibファイルっていうのが出来上がるのですが、そうしたディレクトリ構成を作り上げるものです。
include(GNUInstallDirs)⑨
こちらは、⑧のGNUInstallDirsと関わるのですが、Library(ライブラリ系のモジュールは、⑧で作成したlibディレクトリに、実行ファイル系のモジュールは、binディレクトリに入れる、と言ったことが書かれています。
install(TARGETS FirstProject
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)⑩
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(FirstProject)
endif()先で見たMANUAL FINALIZATIONを思い出してください。手動で最終処理を行う、という意味でしたが、ここで最終処理を行っています。最終処理ってそもそも何?ということも重要になりますが、そこはいずれ話したいと思います。
CMakeってQtなの?
Qtではありません。CMakeというのはビルド用の記述を行う一つのプログラミング言語みたいなものです。Qtも外部の記法を輸入しているわけですね。例えば、uiファイルは、xmlコードで書かれています。
CMake Documenationには、Qt用の特別枠の関数の説明があります。
また、Qt自体も、CMakeLists.txtで使用する独自の関数を用意しています。
Qtを学ぶのにCMakeLists.txtの内容を理解することは必須ではありませんが、ここでトラブルにあうことは、実はそれなりにあります。問題の発生原因はさまざまですが、端的に言えば、CMakeLists.txtに書かれていることと、自分のローカル環境の設定のズレが原因です。ここら辺の知識を習得することをサボっていると、よくありません。早く開発したい!と思うのはわかりますが、開発環境が動く根っこの部分を理解しておいた方がいいですし、何より頭のノイズが減ってスッキリします。
私の記事では、CMakeに関する記事も書きますので、一緒に学習していきましょう。
また、CMakeLists.txtを書く言語としては、Linuxのシェルスクリプトと同じ記法が利用されています。${}は、変数の参照で、if endif等からも、それが匂いますね。
ここは研究がてら、追って更新いたします。なお、CMakeのリファレンスも翻訳したいと思います。これは別記事で行います。
では、早速ヘッダファイルとソースファイルの読み解きに行きましょう。
MainWindow.h
//二重インクルードガード
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#QMainWindowファイルをインクルード
#include <QMainWindow>
//マクロ関数 namespace Qt6{
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
//}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_HC++の基本ですが、二重インクルードガード、ライブラリのインクルード、こちらは割愛します。
QT_BEGIN_NAMESPACEとQT_END_NAMESPACEの謎
次に、QT_BEGIN_NAMESPACEというマクロが置いてありますが、これは何のためかというと、Qtのバージョンが変更された時に、名前空間の衝突を回避するためのもの(らしい)です。すごい仰々しい名前が付けられているように見えますが、実態は、
namespace Qt_NAMESPACE {がQT_BEGIN_NAMESPACEの正体で、
}がQT_END_NAMESPACEの正体のようです。
この謎を解くには、このファイルを見ます。
上のリンクのページを読んでもらえたらわかりますが、
#elif defined(QT_INLINE_NAMESPACE) /* user inline namespace FIXME in Qt 7: Default */
QT_INLINE_NAMESPACEが定義されていれば、置き換えが発生し、そうでなければ、空文字なので、何も入れ込まれていないことになります。で、コメントが書かれているように、Qt7では、それをデフォルトにする意向のようですね。
じゃあ、その場合は、Qt6::Ui::MainWindowっていう形で指定しなくちゃいけないの?って気になりますが、using namespace Qt6っていうコードが書かれることになって、結局Ui::MainWindowのまま使えるようになっているらしいです。
後、注意ですが、ここで書かれている内容は、前方宣言です。ここでネームスペースが定義されているわけではありません。詳細は後述します。
素人さんたちは気にしなくてよくなってるよ。ってことなので、あまり気にしなくてもいいかもですが、やっぱりQt独自の謎の部分はあり、ここはもっと深く学ばなければならないかもしれませんね。
で、話の続きに行きますが、次は普通のクラス定義ですね。
Q_OBJECT
だけど、Q_OBJECTというまたまた見慣れないマクロ定義があります。これを理解しておくのは、重要なのですが、初学の人には難しいです。後、マクロ定数を使うっていうのは、プログラマの中ではあまり好まれないようなんですが、Qtはマクロ定義が多いです。マクロ好きな人が開発しているのか、と思いたくなりますが、ちゃんとした理由がないとなかなかそうはならないと思いますね。
Q_OBJECTは、mocというファイルをQtが扱っていることと関係しています。mocとは、メタオブジェクトコンパイラの略です。Qtファイルがコンパイルされるとき、特殊な情報を読み込ませるのです。
そして、Qtはどうやらmocファイルという特別なファイルを作り、そこで、親子関係やシグナル&スロットの関係などを管理しています。
QObjectを継承したクラスであれば、このQ_OBJECTは必ずつけられます。
MainWindow.cpp
#include "MainWindow.h"
#include "./ui_MainWindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
はい。これはおなじみさんで、最初にヘッダファイルをインクルードします。2番目のインクルードは、デザイナファイルです。これはどうして出来上がるのかというと、皆さんがプロジェクトを作成したときに、

これを指定していたからです。mocと同じように、uicというコンパイラがQtには存在します。これは、デザイナファイルをQtのソースコードとして取りこむ作業も行っています。なので、そうしてできたヘッダファイルをインクルードしています。
ここで、Ui::MainWindowという形で、Ui用に名前空間を分けている理由がわかりますよね?MainWindowクラスとUi::MainWindowと名前を分けなければ衝突してしまいます。
MainWindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>これがuiファイルです。これはxmlと言われる記述で書かれています。このままではQtのライブラリでは使えません。だから、この情報を読み取って、C++のコードに置き換えます。これを変換したファイルは、autogenファイルに入っています。
/********************************************************************************
** Form generated from reading UI file 'MainWindow.ui'
**
** Created by: Qt User Interface Compiler version 6.8.2
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_MAINWINDOW_H
#define UI_MAINWINDOW_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_MainWindow
{
public:
QWidget *centralwidget;
QMenuBar *menubar;
QStatusBar *statusbar;
void setupUi(QMainWindow *MainWindow)
{
if (MainWindow->objectName().isEmpty())
MainWindow->setObjectName("MainWindow");
MainWindow->resize(800, 600);
centralwidget = new QWidget(MainWindow);
centralwidget->setObjectName("centralwidget");
MainWindow->setCentralWidget(centralwidget);
menubar = new QMenuBar(MainWindow);
menubar->setObjectName("menubar");
menubar->setGeometry(QRect(0, 0, 800, 22));
MainWindow->setMenuBar(menubar);
statusbar = new QStatusBar(MainWindow);
statusbar->setObjectName("statusbar");
MainWindow->setStatusBar(statusbar);
retranslateUi(MainWindow);
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
void retranslateUi(QMainWindow *MainWindow)
{
MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "MainWindow", nullptr));
} // retranslateUi
};
namespace Ui {
class MainWindow: public Ui_MainWindow {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_MAINWINDOW_H
classとしては、Ui_MainWindowとなっていますが、注目していただきたいのが、setupUiの引数です。QMainWindow*型のMainWindowとなっていますね。同じ名前をここで使いたいんだけど、MainWindow.hとMainWindow.cppでMainWindowとしちゃうと、名前が衝突してしまう。だから、ああやって切り分けているのです。
このファイルが読み込まれます。もう少しQtのことがわかってくると、何で最初からこれ使えるんだろ?って不思議に思うこともあるでしょう。背後ではこういうことが行われているんですね。
先ほど、MainWindow.hで、ネームスペースが宣言されているのではなく、ここで行われているのは前方宣言にしか過ぎない、と申し上げましたが、このファイルの最後の記述
namespace Ui {
class MainWindow: public Ui_MainWindow {};
} // namespace Uiここでnamespaceが定義されています。
これは「Ui::MainWindow という名前のクラスを定義し、それが Ui_MainWindow を継承する」という意味です。
クラス本体(中括弧 {})に何も書かれていませんが、MainWindow クラスは Ui_MainWindow の全てのメンバー機能(変数や関数)をそのまま引き継ぎます。つまり、空の継承クラスですが、まったく問題なく使えますし、目的に応じた軽量な“ラッパー”になります。
main.c
#include "MainWindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
はい。これがmain関数です。今までのファイルと違って、ちんまいですね。
main関数は、エントリポイントと呼ばれ、コードが最初に実行されるところですね。
QApplicationインスタンスを作成します。この後でなければ、QWidgetsに属するコンポーネントは使えない、これは常識になります。
そして、Qtはイベントドリブンでアプリを回しますので、無限ループが始まります。a.exec()がそれを行います。常に状態を監視します。
アプリが正常終了したら、a.exec()は1を返します。無限ループも終わりということですね。
まとめ
Qtプログラムを動かすには、
CMakeLists.txt が必要になります。これがあると、そこがプロジェクトのメインディレクトリだ、という意味になります。
シェルスクリプトをベースに記述されたプログラミング言語です。
CMakeはQtと独立して存在していますが、Qtライブラリ自体の存在を認め、Qt用に特別な関数を用意しています。
Qt自身も、CMake用の特別な関数を用意しています。
Qtには、moc, uic, rccという特別な3種類のコンパイラがあります。
mocは、メタオブジェクトコンパイラ
uicは、ユーザーインターフェースコンパイラ
rccは、リソースコンパイラ
QtはC++がベースですが、動的メモリ確保を頻繁に行います。とすれば、メモリの解放をはじめとした、メモリ管理の必要性がある言語です。しかし、Qtはそれを特別な仕様にしています。
それは、親子関係です。setParentという関数で、あるコンポーネントの親を設定できます。親のメモリが解放されたら、子のメモリも解放される、という仕組みになっています。そのため、非常にメモリ管理が楽な仕組みになっています。Q_OBJECTは、mocに対して、そうしたことを伝えるマクロでもあります。
次に、シグナル&スロットという特殊なイベント管理を行うことができます。シグナル&スロットは、あるコンポーネントで発生したイベントを、他のコンポーネントへ知らせることができる、エンドツーエンドの仕組みです。ということは、どのコンポ―ネントと、どのコンポーネントがつなげられて、何のシグナルが送られた時、どのスロットが呼ばれるのか?と言ったことを登録しておく必要があるのですが、mocはそれについても事前にコンパイルします。
uicは、xmlファイルで記述されたGUIフォーマットを、実行可能なソースファイルへ変換します。
rccは、イメージ画像や、音楽ファイル等のリソースファイルをコンパイルするためのコンパイラです。こちらの使い方も、また後で見ることになります。
Qtの特徴としては、マクロが多いです。だからと言って、私たちが開発を行うときは、マクロを使いまくることはやめた方がいいでしょう。
QtはQWidget, QDialog, QMainWindowの3つをQWidgetアプリケーションを選択した場合に、土台のコンポーネントとして用意しています。
QMainWindow.hと、QMainWindow.cppができるのはそれをプロジェクト作成時に選択したからです。
main.cppファイルは、恐らくどんな場合でも作成されるエントリポイントです。
Qtは、QWidget系コンポーネントを利用して開発を行う場合、QApplicationインスタンスを作成してからがスタートです。QApplicationをインスタンス化する前に、QWidget系のコンポーネントをインスタンス化してはいけません。
Qtはイベントドリブンで実行されますので、無限ループです。それを行うのが、exec関数です。
とりあえず、こういうところを知っておけば、このお話はいいのではないかと思います。お疲れさまでした。
この記事が気に入ったらチップで応援してみませんか?
