QMLの基礎 - シングルトン
概要
オブジェクト指向のプログラム言語のクラスのデザインパターンの1種である。
デザインパターンとは、オブジェクト指向の言語で使用される設計のパターンのことであり、設計時に直面する問題とその解決策を整理してまとめたものを指す。
シングルトンは、クラスの設計方法の1つであり、クラスのインスタンスを2つ以上生成できないようにすることで、常に同一のインスタンスが参照されるデザインパターンとなる。
例えば、インスタンスの状態を常に持つ場合やクラス端で共通のデータとしてアクセスする必要がある場合に使用される。
前提条件
QQmlEngineヘッダファイルをインクルードする必要がある。
また、qmakeを使用する場合は、変数QTにqmlを追加する必要がある。
// cppファイル
#include <QQmlEngine>
# qmakeファイル QT += qml
QML_SINGLETONマクロ
QMLにおいて、内包する型がシングルトンであることを宣言するには、QObject
クラスを継承したクラスを記述して、QML_SINGLETON
マクロを使用する。
これは、そのクラスがQ_OBJECT
マクロが記述されており、QMLで利用可能である場合(QML_ELEMENT
マクロまたはQML_NAMED_ELEMENT()
マクロを持つ場合)にのみ有効である。
Qt 5の場合、デフォルトでは、各QQmlEngine
クラスは、その型に最初にアクセスした時、その型のデフォルトコンストラクタを使用してシングルトンのインスタンスを生成する。
もし、デフォルトコンストラクタが存在しない場合は、シングルトンは初期状態ではアクセス不能になるため、
qmlRegisterSingletonType()
に特定のファクトリ関数を指定する、または、
qmlRegisterSingletonInstance()
に同じクラスで同じ型の名前空間とバージョンの特定のインスタンスを指定して呼び出すことにより、オーバーライドすることができる。
Qt 6の場合、デフォルトでは、各QQmlEngine
クラスは、その型に最初にアクセスした時、その型のデフォルトコンストラクタ、または、
T *create(QQmlEngine*, QJSEngine*)
というシグネチャを持つ静的ファクトリ関数のどちらかを使用して、シングルトンのインスタンスを生成しようとする。
もし、両方が存在して、かつ、アクセス可能な場合、デフォルトコンストラクタが優先される。
デフォルトコンストラクタも静的ファクトリ関数も存在しない場合は、シングルトンはアクセス不能になることに注意する。
デフォルトコンストラクタを持つクラスをシングルトンとして宣言する場合は、QML_SINGLETON
マクロを記述するだけである。
class MySingleton : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
// Q_PROPERTY( ... )
public:
// メンバ変数 / メンバ関数, Q_INVOKABLEメソッド等
};
シングルトンクラスがデフォルトで構築不可であり修正可能な場合は、アクセス可能にするため、静的ファクトリー関数を追加する。
class MySingleton : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
// Q_PROPERTY( ... )
public:
static MySingleton *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine)
{
MySingleton *result = nullptr;
// 何らかのカスタムコンストラクタやファクトリを用いてオブジェクトを生成する
// QMLエンジンは、最終的にこのオブジェクトの所有権を取得および削除する
return result;
}
// メンバ変数 / メンバ関数, Q_INVOKABLEメソッド等
};
シングルトンクラスが変更不可で、デフォルトコンストラクタや静的ファクトリー関数が存在しない場合、QML_FOREIGNラッパーを使用して、静的ファクトリー関数を定義することが可能である。
struct SingletonForeign
{
Q_GADGET
QML_FOREIGN(MySingleton)
QML_SINGLETON
QML_NAMED_ELEMENT(MySingleton)
public:
static MySingleton *create(QQmlEngine *, QJSEngine *engine)
{
MySingleton *result = nullptr;
// 何らかのカスタムコンストラクタやファクトリを用いてインスタンスを生成する
// QMLエンジンは、最終的にそのインスタンスの所有権を取得および削除する
return result;
}
};
もし、特定のシングルトンオブジェクトを提供する場合、その生成はコントロールできないが、静的ファクトリー関数からそれを返すことができる。
これは、qmlRegisterSingletonInstance
関数の代わりとなるものである。
qmlRegisterSingletonInstance("MyModule", 1, 0, "MySingleton", myObject);
もし、上記のqmlRegisterSingletonInstance("MyModule", 1, 0, "MySingleton", myObject)を記述している場合、MyObjectがMySingleton *型であれば、
代わりに、以下の例のようにクラスを定義することができる。
これは、既存のクラスであるMySingletonクラスは、MySingletonというQMLのシングルトンであることが宣言される。
s_singletonInstanceメンバを設定することにより、使用する前にいつでもそのインスタンスを指定することができるため、MySingletonクラス自身を変更する必要はない。
struct SingletonForeign
{
Q_GADGET
QML_FOREIGN(MySingleton)
QML_SINGLETON
QML_NAMED_ELEMENT(MySingleton)
public:
// Initialize this using myObject where you would previously
// call qmlRegisterSingletonInstance().
inline static MySingleton *s_singletonInstance = nullptr;
static MySingleton *create(QQmlEngine *, QJSEngine *engine)
{
// The instance has to exist before it is used. We cannot replace it.
Q_ASSERT(s_singletonInstance);
// The engine has to have the same thread affinity as the singleton.
Q_ASSERT(engine->thread() == s_singletonInstance->thread());
// There can only be one engine accessing the singleton.
if (s_engine)
Q_ASSERT(engine == s_engine);
else
s_engine = engine;
// Explicitly specify C++ ownership so that the engine doesn't delete
// the instance.
QJSEngine::setObjectOwnership(s_singletonInstance, QJSEngine::CppOwnership);
return s_singletonInstance;
}
private:
inline static QJSEngine *s_engine = nullptr;
};
※注意
このパターンは、シングルトンが複数のQMLエンジンからアクセスされる場合、または、アクセスするQMLエンジンがシングルトンオブジェクト自身と異なるスレッドアフィニティを持っている場合は動作しない。
上記で示したように、create()
メソッドのパラメータでエンジンのIDやスレッドアフィニティを確認することにより、アサーションすることができる。