本文へスキップします。

本文へ

【全Qt】
【全・Qt】SRAロゴ
H1

Qt 6.12 への移行実践

技術記事

(掲載 2026年10月24日)

Qt 6 への移行

Qt 5 から Qt 6 への移行方針が決まったら、次は対象の Qt バージョンを固定し、実際のコードと配布物を確認します。本記事では Qt 6.12.0 を使い、ビルド設定、C++ の修正、QML の初期化、配布時の確認を具体化します。

移行対象の棚卸し、段階的な移行、見積もりの考え方は、基本編「Qt 5 から Qt 6 への移行」を参照してください。本記事は基本編の続編であり、元動画の内容を Qt 6.12 向けに読み替えたものではありません。Qt 6.12 の公式文書と、独自の小規模サンプルによる検証をもとにしています。

1. この記事で扱う範囲と検証環境

この記事でいう「Qt 6.12 への移行」には、Qt 5 から移る際の修正と、Qt 6.12 を採用する際の追加確認の両方が含まれます。すべてが Qt 6.12 で新しく発生した変更ではありません。

区分 本記事で扱う例
Qt 5 から Qt 6 への変更 独自の qHash()、QComboBox の文字列引数のシグナル
Qt 6 系で利用できる構成 qt_add_qml_module()、QML の必須プロパティへの初期値渡し
Qt 6.12 採用時の確認 対応環境、QML の診断、条件付きの qt.conf 自動生成
利用モジュールに依存する確認 Qt Charts の非推奨化と Qt Graphs の選択

検証日は 2026 年 10 月 24 日です。

項目 検証環境
Qt Qt 6.12.0、macOS 用 arm64 バイナリ
OS macOS 26.6.2、arm64
開発環境 Xcode 26.6、Apple Clang 21.0.0
CMake 3.31.12
ビルド構成 Release、C++17
実行方法 QT_QPA_PLATFORM=offscreen、自動判定

検証用コードは 末尾の検証用コード にあります。コンパイル、コンテナーの検索、Widgets のシグナル、QML オブジェクトの生成を確認しています。Qt 5 環境との比較実行、画面描画、GPU、実際のマウス・キーボード操作は検証していません。既存製品全体の移行を完了したという意味ではありません。

2. Qt 6.12 を採用するための具体的な条件

まず、開発者の端末と顧客の端末を分けて確認します。開発端末でビルドできても、配布先の OS やランタイムが対象外であれば、そのまま採用することはできません。

Qt 6.12 の公式対応表には、たとえば次の条件が示されています。ここでは代表的な構成だけを抜き出しています。

対象 採用前に確認する条件
macOS 対象 OS は macOS 14.4 以降。ビルド環境は Xcode 16/macOS 15 SDK 以降
Windows x86_64 Windows 10 1809 以降または Windows 11。MSVC 2022、MinGW-w64 15.1 など、表に記載された組合せ
Linux ディストリビューションとコンパイラーの組合せに加え、配布先の glibc を確認。インストーラー提供バイナリは Ubuntu 24.04/glibc 2.39 を基準とする

Qt 6.12 は Windows 10 をサポートする最後の Qt バージョンとされています。Windows 10 を残す製品では、今回動くかという確認に加えて、将来の OS 更新計画も必要です。公式表にない構成は、動作する可能性と公式サポートの有無を分けて判断してください。公式対応環境

実務では「Qt 6.12 を使用」とだけ記録せず、Qt のパッチ番号、コンパイラー、CPU アーキテクチャー、配布先 OS を一組にします。外部ライブラリも同じ条件で使用できるかを確認します。特に、別のコンパイラーで作られた C++ ライブラリを、そのままリンクできるとは限りません。

本記事では macOS の一構成を確認しました。Windows と Linux の条件は公式文書による確認であり、実機での検証結果ではありません。

3. Qt 6.12 用のビルド設定を用意する

既存プロジェクトを一度に書き換える前に、使用する Qt を固定した小さなターゲットを作ります。これにより、開発環境の問題とアプリケーション固有の問題を分けやすくなります。

検証用プロジェクトの通常ビルド部分は、次の構成です。

cmake_minimum_required(VERSION 3.21)
project(MigrationProbe LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets Qml Quick Test)
qt_standard_project_setup(REQUIRES 6.12)

qt_add_executable(migration_probe main.cpp)
set_target_properties(migration_probe PROPERTIES MACOSX_BUNDLE TRUE)

qt_add_qml_module(migration_probe
    URI MigrationProbe
    VERSION 1.0
    QML_FILES Probe.qml
)

target_link_libraries(migration_probe
    PRIVATE Qt6::Widgets Qt6::Qml Qt6::Quick Qt6::Test
)

enable_testing()
add_test(NAME migration_probe COMMAND migration_probe)
set_tests_properties(migration_probe PROPERTIES
    ENVIRONMENT "QT_QPA_PLATFORM=offscreen"
)

Qt6::Test はシグナルの発生回数を調べる QSignalSpy のために使用しています。製品の実行ファイルすべてに必要な依存ではありません。また、サンプルの QML は画面を描かない QtObject であり、Quick をリンクしても描画検証をしたことにはなりません。

qt_standard_project_setup() は、最初の find_package() の直後、ターゲット定義の前に呼び出します。REQUIRES 6.12 は、それより古い Qt を拒否するとともに、Qt 6.12 までの CMake ポリシーを新しい動作に設定します。Qt 6.12.0 だけを厳密に指定するものではないため、使用するインストール先も指定します。qt_standard_project_setup

Qt 5 や別の Qt 6 で使ったビルドディレクトリーを再利用せず、次のように新規に構成します。パスは各自の環境に置き換えてください。

cmake -S qt612-validation -B build-qt612 \
  -DCMAKE_PREFIX_PATH=/usr/local/qt/Qt/6.12.0/macos \
  -DCMAKE_BUILD_TYPE=Release
cmake --build build-qt612 --parallel 4
ctest --test-dir build-qt612 -V
cmake --build build-qt612 --target migration_probe_qmllint

この例は今回の macOS 環境向けです。Visual Studio などの複数構成ジェネレーターでは、ビルドに --config Release、CTest に -C Release を指定します。

検証では通常ビルド、CTest、qmllint ターゲットが成功しました。実行時にも qVersion() で 6.12.0 と表示されることを確認しました。構成時の指定だけでなく、実際にロードされた Qt も確認するためです。

Qt 6.12 の qt.conf 自動生成に注意する

Qt 6.12 の qt_add_qml_module() には、実行ファイルをモジュールの基盤とし、CMake ターゲットによる QML 依存関係を指定する場合などに、ビルドツリー用の qt.conf を自動生成する仕組みがあります。既存の qt.conf を使う構成では、生成物と設定内容の競合を確認します。

NO_GENERATE_QTCONF は自動生成を無効にする選択肢ですが、その場合は必要な QML import パスを自分で設定する必要があります。警告や不具合の原因を確認せず、一律に無効化する設定にはしないでください。これはすべてのアプリケーションで同じファイルが生成されるという意味でも、配布物の完成を保証するものでもありません。qt_add_qml_module

4. C++・Widgets の移行で確認した問題と修正

基本編で紹介した文字列ビューやサイズ型の説明は繰り返さず、ここでは別の二つの確認例を扱います。いずれも Qt 6.12 固有ではなく、Qt 5 から Qt 6 への移行時に確認する事項です。

独自キーの qHash() を確認する

機器名とチャンネル番号のような複合キーを QHash に使う場合、独自の qHash() が必要になります。Qt 6 では戻り値と seed の型に size_t を使います。関数はキー型と同じ名前空間に置き、等価比較に使うメンバーとハッシュに使うメンバーを整合させます。Qt Core の Qt 6 での変更

namespace Domain {
struct Key {
    QString device;
    int channel;

    friend bool operator==(const Key &a, const Key &b)
    {
        return a.device == b.device && a.channel == b.channel;
    }
};

size_t qHash(const Key &key, size_t seed = 0) noexcept
{
    return qHashMulti(seed, key.device, key.channel);
}
}

検証では、("pump", 1) を登録して同じキーで値を取得できることと、("pump", 2) が未登録と判定されることを確認しました。ハッシュ値そのものが特定の数値になることはテストしていません。確認したいのはキーの検索が正しく働くことだからです。

実際のアプリケーションでは、空文字列、境界値、大量のキー、既存データの読み戻しも追加します。今回の二つの確認だけで、性能や全入力の正しさまで検証したとはいえません。

QComboBox は処理の目的からシグナルを選ぶ

Qt 5 のコードに、次の文字列引数のオーバーロードを指定する接続が残っている場合があります。

QObject::connect(combo,
    QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
    receiver, [](const QString &) {});

このコードを Qt 6.12.0 でビルドすると、of に適合する関数がないというコンパイルエラーになりました。失敗を再現するコードは legacy_combo.cpp に分離し、通常のビルド対象から外しています。

修正時には、名前が似ている currentTextChanged() に機械的に置換する前に、元の処理が何を監視したいのかを確認します。

監視したいこと 使用するシグナル
選択位置の変化 currentIndexChanged(int)
現在の表示文字列の変化 currentTextChanged(const QString &)
ユーザーによる選択操作 activated(int)

これらは同じ意味ではありません。インデックスと文字列の変更通知はプログラムからの変更でも発生しますが、activated() はユーザー操作に対応する通知です。QComboBox

検証では、表示文字列がどちらも Ready で、項目データだけが 101 と 202 の二項目を作りました。プログラムから二番目を選ぶと、結果は次のようになりました。

確認対象 結果
currentIndexChanged 1 回
currentTextChanged 0 回
activated 0 回
currentData() 202

このような項目を文字列だけで識別すると、選択変更を見落とします。機器 ID などを扱う場合は項目データに保持し、インデックスの変更時に取得する構成が考えられます。

QObject::connect(combo, &QComboBox::currentIndexChanged,
                 receiver, [combo](int index) {
    if (index < 0)
        return;
    const int deviceId = combo->itemData(index).toInt();
    // deviceId を使って選択対象を更新する
});

今回確認したのはプログラムから変更した場合です。マウス、キーボード、同じ項目の再選択などは、製品の操作テストとして別途確認します。

5. QML・Qt Quick の移行で確認した問題と修正

QML では「読み込めたか」だけでなく、オブジェクト生成時に必要な値が渡されているかを確認します。ここでは画面や GPU の影響を切り離すため、最小の QML オブジェクトを使います。

import QtQml

QtObject {
    required property string deviceName
    readonly property string caption: "Device: " + deviceName
}

required は Qt 6.12 で初めて導入されたものではありません。一方、Qt 6.12 の変更情報には、必須プロパティが不足したコンポーネントの生成時にエラーを報告する動作の変更が記載されています。今回のテストは Qt 6.12.0 での結果であり、以前の Qt が同じテストでどのように動作するかは比較していません。Qt 6.12 の変更情報

初期値はオブジェクトの生成時に渡す

この QML を初期値なしの create() で生成したところ、オブジェクトは生成されず、次の診断を確認しました。

Required property deviceName was not initialized

生成後の setProperty() で埋めようとしても、生成そのものに失敗した場合には間に合いません。C++ から生成するこの例では、createWithInitialProperties() に初期値を渡します。

QQmlComponent component(
    &engine, QUrl(QStringLiteral("qrc:/qt/qml/MigrationProbe/Probe.qml")));

std::unique_ptr<QObject> object(component.createWithInitialProperties({
    {QStringLiteral("deviceName"), QStringLiteral("pump")}
}));

if (!object) {
    for (const auto &error : component.errors())
        qWarning().noquote() << error.toString();
    return 1;
}

createWithInitialProperties() も Qt 6.12 の新機能ではありません。生成時に初期値を渡すための既存 API を使い、必須値の不足を避ける例です。QQmlComponent

サンプルでは、初期値を渡すと生成に成功し、caption が Device: pump になることを確認しました。その後 deviceName を motor に変更すると、バインディングによって Device: motor に更新されることも確認しています。

画面を持つアプリケーションでは追加の確認が必要

今回の QtObject の検証では、画面の描画結果を判断できません。実際の Qt Quick 画面では、既存の操作を同じ入力で再現し、表示サイズ、文字の折返し、フォーカス、モデルの更新、描画バックエンドに依存する処理を確認します。

また、qmllint の成功は、実行時のすべての状態で正しく動くことを保証しません。静的な検査、生成時のエラー確認、実画面の操作確認をそれぞれ行います。

6. 特定のモジュールを使う場合の追加対応

ここはすべてのアプリケーションに必要な作業ではありません。使用しているモジュールに該当する部分だけを移行項目に追加します。

Qt Charts を使っている場合

Qt Charts は Qt 6.10 から非推奨です。「Qt 6.12 で初めて非推奨になった」「Qt 6.12 では削除された」とは区別してください。Qt Charts

Qt Graphs への変更を検討する場合は、単にリンク先を変える作業として扱わず、必要なグラフ、軸、操作、ラベル、画面への組込み方を一つずつ確認します。既存グラフの代表例を小さく作り直し、同じデータで表示と操作を比較すると、変更範囲を判断しやすくなります。

Qt 6.12 の Qt Graphs には、2D グラフ向けの Qt Canvas Painter を使う選択肢などが追加されています。ただし、設定条件のある機能を、通常のインストールですべて有効になる前提にはできません。Qt 6.12 の変更情報

Qt Graphs の利用条件も、実際に採用するライセンスで確認します。モジュール名が Qt に含まれることだけを理由に、別モジュールと同じ条件だと判断しないでください。Qt Graphs

本記事では Qt Graphs の実装比較や性能測定を行っていません。ここで示したのは、該当するアプリケーションで追加する確認事項です。

その他のプラグインや外部依存がある場合

SQL ドライバー、画像形式、通信ライブラリ、独自プラグインなどは、コードのビルド成功とは別に配布確認が必要です。たとえば開発端末だけに入っているドライバーを暗黙に利用すると、配布後にはじめて不足が分かります。

代表的なデータを実際に読み込む、対象のデータベースに接続するなど、製品の機能に対応した確認を用意します。今回のサンプルにはこれらの依存がないため、検証対象には含めていません。

7. 配布物を作成し、開発環境のない端末で確認する

移行の完了条件は、ビルド成功だけではありません。配布物に必要なライブラリ、プラグイン、QML モジュールが含まれ、対象端末で実行できることを確認します。

macOS では、ビルドしたアプリケーションのコピーに対して macdeployqt を実行する方法があります。今回もビルド成果物とは別の場所にコピーを作り、検証しました。

mkdir -p package
cp -R build-qt612/migration_probe.app package/
/usr/local/qt/Qt/6.12.0/macos/bin/macdeployqt \
  package/migration_probe.app \
  -qmldir="$PWD/qt612-validation" -verbose=2

-qmldir には QML のソースディレクトリーを指定します。配布ツールが処理する依存に加えて、アプリケーション固有の外部ライブラリやデータも確認してください。署名や公証の手順は配布形態に応じて別途必要です。macOS の配布

今回確認した範囲と、次に行う確認

標準の macdeployqt 実行後、テスト起動はプラットフォームプラグインの探索エラーで失敗しました。配布物を調べると、検証用の offscreen プラグインが含まれておらず、さらに Contents/Resources/qt.conf にビルドディレクトリーを参照する設定が残っていました。

まず Qt の plugins/platforms/libqoffscreen.dylib をアプリケーションの Contents/PlugIns/platforms/ に追加し、macdeployqt を再実行しました。ただし、プラグインの追加だけでは解消しませんでした。今回の構成では、配布用の qt.conf を次のように設定した後、起動できました。

[Paths]
Prefix = ..
Plugins = PlugIns
QmlImports = Resources/qml

これは今回作成したバンドルと起動方法で確認した設定です。他の構成にそのままコピーせず、実際の配置と探索先を確認してください。offscreen の追加も今回の自動テスト用であり、通常の GUI 配布に一律に必要な手順ではありません。変更後のローカル検証ではアドホック署名を付け直しました。これは配布用の Developer ID 署名や公証を済ませたという意味ではありません。

Qt 関連の環境変数を引き継がず、QT_QPA_PLATFORM=offscreen を明示して実行すると、配布前と同じ 10 項目がすべて成功しました。失敗時のログ、元の qt.conf、修正後の実行結果は、記事作成時の検証記録として保管しています。

今回の配布テストは、Qt をインストールしてある同じ開発端末上での確認です。開発環境のない別端末での実行を確認したわけではありません。

製品として配布する際は、対象 OS と CPU の別端末または検証環境に配布物だけをコピーし、次を確認します。

  1. Qt や開発ツールをインストールしていない状態で起動する。
  2. 通常の起動方法で画面を開き、主要な操作を実行する。
  3. 画像、翻訳、QML、各種プラグイン、外部データが読み込まれる。
  4. 配布先固有の権限、ネットワーク、ドライバーで動作する。
  5. 署名、公証、インストール・更新・削除の運用を確認する。

開発端末上での成功を、別端末での成功として記録しないことが重要です。とくに今回のサンプルは offscreen 実行なので、通常の macOS 画面表示や Gatekeeper による起動確認の代わりにはなりません。

8. 基本編に追加する確認事項

基本編の棚卸しや移行計画に、今回の対象バージョンと実装例から得た確認を追加します。

確認事項 今回の状態 製品への適用時に残る作業
Qt と開発環境の固定 Qt 6.12.0 の一構成で確認 顧客の OS、CPU、コンパイラーとの対応を確認
CMake と QML モジュール 通常ビルドと qmllint 成功 製品の依存関係、独自 qt.conf を確認
独自キーのハッシュ 同一キーの検索と別キーの未登録を確認 境界値、大量データ、既存データを確認
コンボボックスの選択 同一表示文字列の項目切替を確認 実際のユーザー操作と後続処理を確認
QML の初期値 不足時の拒否、生成、更新を確認 実画面、モデル、非同期処理を確認
グラフなどの追加モジュール 文書上の確認 使用する機能の試作・比較
配布 同じ開発端末で検証 開発環境のない対象端末で確認

コードの修正一覧には、修正理由だけでなく「何を確かめれば完了か」を添えます。コンボボックスなら、コンパイルが通ることに加え、同じ文字列の項目も正しく識別できることが完了条件になります。

Qt 6.12 を採用する作業では、基本編で決めた移行範囲を、このような具体的なテストと配布確認に落とし込みます。未確認の部分を明示して残すことで、次に必要な作業と移行判断の根拠を共有できます。

移行を相談するときに用意するとよい情報

既存アプリケーションへの適用や、対象環境でのビルド・動作・配布の確認に課題がある場合は、SRA にご相談ください。相談時に次の情報を共有いただくと、具体的な検討を進めやすくなります。

  • 使用中・移行先の Qt のバージョン
  • 対象 OS、コンパイラ、ビルド環境
  • 利用モジュールと、Widgets・QML の使用状況
  • ビルド・動作・配布のどこで困っているか
  • エラーメッセージや再現手順、確認済みの内容

最初からすべてを調べ切る必要はありません。分かる範囲で共有し、不明な項目は不明としてお伝えください。

支援内容は、Qt に関する SRA のサービスをご覧ください。

付録:検証用コード

以下の4ファイルを qt612-validation ディレクトリーに保存すると、第3章の手順で検証できます。legacy_combo は意図したコンパイル失敗の再現用です。

CMakeLists.txt

cmake_minimum_required(VERSION 3.21)
project(MigrationProbe LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Widgets Qml Quick Test)
qt_standard_project_setup(REQUIRES 6.12)
qt_add_executable(migration_probe main.cpp)
set_target_properties(migration_probe PROPERTIES MACOSX_BUNDLE TRUE)
qt_add_qml_module(migration_probe
    URI MigrationProbe
    VERSION 1.0
    QML_FILES Probe.qml
)
target_link_libraries(migration_probe PRIVATE Qt6::Widgets Qt6::Qml Qt6::Quick Qt6::Test)
enable_testing()
add_test(NAME migration_probe COMMAND migration_probe)
set_tests_properties(migration_probe PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
# Expected compile failure: a Qt 5-style string overload removed from Qt 6.
add_library(legacy_combo OBJECT EXCLUDE_FROM_ALL legacy_combo.cpp)
target_link_libraries(legacy_combo PRIVATE Qt6::Widgets)

main.cpp

#include <QApplication>
#include <QComboBox>
#include <QHash>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QSignalSpy>
#include <QDebug>
#include <memory>

namespace Domain {
struct Key {
    QString device;
    int channel;
    friend bool operator==(const Key &a, const Key &b) {
        return a.device == b.device && a.channel == b.channel;
    }
};
size_t qHash(const Key &key, size_t seed = 0) noexcept {
    return qHashMulti(seed, key.device, key.channel);
}
}

int main(int argc, char **argv) {
    QApplication app(argc, argv);
    int failures = 0;
    auto check = [&](bool ok, const char *name) {
        qInfo().noquote() << (ok ? "PASS" : "FAIL") << name;
        if (!ok) ++failures;
    };
    qInfo() << "Qt runtime:" << qVersion();
    QHash<Domain::Key, QString> values;
    values.insert({QStringLiteral("pump"), 1}, QStringLiteral("ready"));
    check(values.value({QStringLiteral("pump"), 1}) == QStringLiteral("ready"), "custom_hash_lookup");
    check(!values.contains({QStringLiteral("pump"), 2}), "custom_hash_distinct_key");

    QComboBox combo;
    combo.addItem(QStringLiteral("Ready"), 101);
    combo.addItem(QStringLiteral("Ready"), 202);
    QSignalSpy indexSpy(&combo, &QComboBox::currentIndexChanged);
    QSignalSpy textSpy(&combo, &QComboBox::currentTextChanged);
    QSignalSpy activatedSpy(&combo, &QComboBox::activated);
    combo.setCurrentIndex(1);
    check(indexSpy.count() == 1, "combo_index_changed_once");
    check(textSpy.count() == 0, "combo_same_text_no_text_signal");
    check(activatedSpy.count() == 0, "combo_programmatic_no_activated");
    check(combo.currentData().toInt() == 202, "combo_identity_from_item_data");

    QQmlEngine engine;
    QQmlComponent missing(&engine, QUrl(QStringLiteral("qrc:/qt/qml/MigrationProbe/Probe.qml")));
    std::unique_ptr<QObject> bad(missing.create());
    check(!bad && missing.isError(), "qml_required_missing_rejected");
    for (const auto &error : missing.errors()) qInfo().noquote() << error.toString();
    QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/qt/qml/MigrationProbe/Probe.qml")));
    std::unique_ptr<QObject> object(component.createWithInitialProperties({{QStringLiteral("deviceName"), QStringLiteral("pump")}}));
    check(bool(object), "qml_create_with_initial_properties");
    if (object) {
        check(object->property("caption").toString() == QStringLiteral("Device: pump"), "qml_initial_binding");
        object->setProperty("deviceName", QStringLiteral("motor"));
        check(object->property("caption").toString() == QStringLiteral("Device: motor"), "qml_binding_after_update");
    } else {
        for (const auto &error : component.errors()) qInfo().noquote() << error.toString();
    }
    qInfo() << "Failures:" << failures;
    return failures ? 1 : 0;
}

Probe.qml

import QtQml
QtObject {
    required property string deviceName
    readonly property string caption: "Device: " + deviceName
}

legacy_combo.cpp

#include <QComboBox>
#include <QObject>
void connectLegacy(QComboBox *combo, QObject *receiver) {
    QObject::connect(combo, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
                     receiver, [](const QString &) {});
}