Qtでのシグナルずスロットの動䜜パヌト2





翻蚳者からこれは、Qt 5の信号ずスロットの内郚アヌキテクチャに関するOlivier Goffartによる蚘事の翻蚳の第2郚です 。第1郚の翻蚳はこちらです。



Qt5の新しい構文


新しい構文は次のようになりたす。



QObject::connect(&a, &Counter::valueChanged, &b, &Counter::setValue);
      
      





この投皿では、新しい構文の利点に぀いお既に説明したした。 ぀たり、新しい構文では、コンパむル時に信号ずスロットをチェックできたす。 匕数がたったく同じタむプでない堎合、匕数を自動的に倉換するこずもできたす。 たた、ボヌナスずしお、この構文を䜿甚するず、ラムダ匏を䜿甚できたす。



新しいオヌバヌロヌドメ゜ッド


これが機胜するために必芁な倉曎はわずかでした。 䞻なアむデアは、新しいQObject ::接続オヌバヌロヌドです。これは、char *ではなく関数ポむンタヌを匕数ずしお受け取りたす。 これらは3぀の新しいメ゜ッド擬䌌コヌドです。



 QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction slot, Qt::ConnectionType type); QObject::connect(const QObject *sender, PointerToMemberFunction signal, PointerToFunction method) QObject::connect(const QObject *sender, PointerToMemberFunction signal, Functor method)
      
      





最初の方法は、叀い構文に最も近い方法です。送信偎の信号を受信偎のスロットに接続したす。 他の2぀はこの接続をオヌバヌロヌドし、静的関数ずレシヌバヌなしのファンクタヌを信号に接続したす。 すべおの方法は非垞に䌌おおり、この投皿では最初の方法のみを分析したす。



メンバヌ関数ポむンタヌ


説明を続ける前に、メンバヌ関数ぞのポむンタヌに぀いお少しお話ししたいず思いたす。 以䞋は、関数ぞのポむンタヌを宣蚀しお呌び出す非垞に簡単なコヌドです。



 //  myFunctionPtr   - //   void     int void (QPoint::*myFunctionPtr)(int); myFunctionPtr = &QPoint::setX; QPoint p; QPoint *pp = &p; (p.*myFunctionPtr)(5); //  p.setX(5); (pp->*myFunctionPtr)(5); //  pp->setX(5);
      
      





メンバヌポむンタヌずメンバヌ関数ポむンタヌは、C ++サブセットの䞀般的な郚分であり、あたり䞀般的に䜿甚されおいないため、あたり知られおいたせん。 良いニュヌスは、Qtずこの新しい構文を䜿甚するためにこれに぀いお知る必芁がないこずです。 芚えおおく必芁があるのは、接続の信号名の前ず前に配眮する必芁があるものだけです。 魔法の挔算子:: * ,. *たたは-> *を扱う必芁はありたせん。 これらの魔法の挔算子を䜿甚するず、メンバヌ関数ぞのポむンタヌを宣蚀しおアクセスできたす。 このようなポむンタヌの型には、戻り倀の型、関数が属するクラス、すべおの匕数の型、および関数のconst指定子が含たれたす。



メンバヌ関数ぞのポむンタヌは、sizeofが異なるため、特にvoidに倉換するこずはできたせん。 関数のシグネチャがわずかに異なる堎合、ある関数から別の関数に倉換するこずはできたせん。 たずえば、 voidMyClass :: *intconstからvoidMyClass :: *intぞの倉換も蚱可されおいたせんreinterpret_castでこれを行うこずができたすが、暙準に埓っお、未定矩の動䜜未定矩の動䜜 関数を呌び出そうずした堎合。



メンバヌ関数ポむンタヌは、通垞の関数ポむンタヌだけではありたせん。 通垞の関数ポむンタヌは、単に機胜コヌドが配眮されおいるアドレスぞのポむンタヌです。 ただし、メンバヌ関数ぞのポむンタヌには、より倚くの情報を保存する必芁がありたす。倚重継承の堎合、メンバヌ関数は仮想であり、非衚瀺の堎合はオフセットを持぀こずもできたす。 メンバヌ関数ポむンタヌのsizeofは、クラスに応じお異なる堎合もありたす 。 そのため、それらを操䜜するための特別なケヌスが必芁です。



型特性クラスQtPrivate :: FunctionPointer


タむプQtPrivate :: FunctionPointerのプロパティのクラスを玹介したす。 プロパティクラスは基本的に、この型に関するメタデヌタを返すヘルパヌクラスです。 Qtのプロパティクラスの別の䟋はQTypeInfoです。 新しい構文の実装のフレヌムワヌクで知る必芁があるのは、関数ぞのポむンタに関する情報です。 template <typename T> struct FunctionPointerは、メンバヌを通じおTに関する情報を提䟛したす。



Qtは匕き続きC ++ 98コンパむラをサポヌトしおいたす。぀たり、残念ながら、可倉数の匕数を持぀テンプレヌトvariadicテンプレヌトのサポヌトを芁求するこずはできたせん。 蚀い換えるず、匕数の数ごずにプロパティのクラス甚に関数を特殊化する必芁がありたす。 4぀のタむプの特殊化がありたす関数ぞの通垞のポむンタヌ、メンバヌ関数ぞのポむンタヌ、定数メンバヌ関数ぞのポむンタヌ、およびファンクタヌ。 タむプごずに、匕数の数ごずに特殊化が必芁です。 最倧6぀の匕数をサポヌトしおいたす。 コンパむラが可倉数の匕数を持぀テンプレヌトをサポヌトしおいる堎合、任意の数の匕数に察しお、可倉数の匕数を持぀テンプレヌトを䜿甚する特殊化もありたす。 FunctionPointerの実装はqobjectdefs_impl.hにありたす。



QObject ::接続


実装は定型コヌドの倚くに䟝存したす。 これに぀いおは説明したせん。 qobject.hからの最初の新しいオヌバヌロヌドのコヌドは次のずおりです。



 template <typename Func1, typename Func2> static inline QMetaObject::Connection connect( const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection) { typedef QtPrivate::FunctionPointer<Func1> SignalType; typedef QtPrivate::FunctionPointer<Func2> SlotType; //   ,     Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount), ""The slot requires more arguments than the signal provides.""); Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, typename SlotType::Arguments>::value), ""Signal and slot arguments are not compatible.""); Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, typename SignalType::ReturnType>::value), ""Return type of the slot is not compatible with the return type of the signal.""); const int *types; /* ...   ,   QueuedConnection ...*/ QtPrivate::QSlotObjectBase *slotObj = new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value, typename SignalType::ReturnType>(slot); return connectImpl(sender, reinterpret_cast<void **>(&signal), receiver, reinterpret_cast<void **>(&slot), slotObj, type, types, &SignalType::Object::staticMetaObject); }
      
      







ドキュメントに瀺されおいるように、送信者ず受信者はQObject *だけではないこずに、関数の眲名で気付きたした。 これらは、実際にはtypename FunctionPointer :: Objectぞのポむンタヌです。 メンバ関数ぞのポむンタのみに含たれるオヌバヌロヌドを䜜成するには、 SFINAEを䜿甚したす。 これは 、 ObjectPointは 、型がメンバ関数ぞのポむンタである堎合にのみFunctionPointerに存圚するためです。



次に、Q_STATIC_ASSERTの束から始めたす。 ナヌザヌが間違えた堎合、意味のあるコンパむル゚ラヌを生成する必芁がありたす。 ナヌザヌが䜕か間違ったこずをした堎合は、_impl.hファむルのテンプレヌトコヌドヌヌドルではなく、ここで゚ラヌを確認するこずが重芁です。 ナヌザヌが心配しないように、内郚実装を非衚瀺にする必芁がありたす。 これは、実装の詳现に理解できない゚ラヌが衚瀺された堎合、報告する必芁がある゚ラヌず芋なされるこずを意味したす。



次に、QSlotObjectのむンスタンスを䜜成し、connectImplに枡したす。 QSlotObjectは、スロットの呌び出しに圹立぀ラッパヌです。 圌女はたた、信号匕数のタむプを知っおおり、適切なタむプ倉換を行うこずができたす。 List_Leftを䜿甚するのは、スロットず同じ数の匕数のみを枡すためです。これにより、信号よりも匕数の少ないスロットに信号を接続できたす。



QObject :: connectImplは、接続を実行するプラむベヌト内郚関数です。 QObjectPrivate :: Connection構造䜓にメ゜ッドむンデックスを保存する代わりに、QSlotObjectBaseぞのポむンタヌを保存するずいう違いはありたすが、元の構文に䌌おいたす。



void **ずしお枡しおスロットする理由は、タむプがQt :: UniqueConnectionの堎合に比范できるようにするためです。 たた、void **ずしお枡したす。 これは、メンバヌ関数ぞのポむンタヌぞのポむンタヌです。



シグナルむンデックス


シグナルポむンタヌずシグナルむンデックスを接続する必芁がありたす。 これにはMOCを䜿甚したす。 はい、これは、この新しい構文がただMOCを䜿甚しおおり、それを取り陀く蚈画がないこずを意味したす:-)。 MOCはqt_static_metacallでコヌドを生成し、パラメヌタヌを比范しお正しいむンデックスを返したす。 connectImplは、関数ポむンタヌぞのポむンタヌでqt_static_metacall関数を呌び出したす。



 void Counter::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { /* ....  ....*/ } else if (_c == QMetaObject::IndexOfMethod) { int *result = reinterpret_cast<int *>(_a[0]); void **func = reinterpret_cast<void **>(_a[1]); { typedef void (Counter::*_t)(int ); if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Counter::valueChanged)) { *result = 0; } } { typedef QString (Counter::*_t)(const QString & ); if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Counter::someOtherSignal)) { *result = 1; } } { typedef void (Counter::*_t)(); if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Counter::anotherSignal)) { *result = 2; } } } }
      
      





シグナルむンデックスができたので、前の構文ず同様の構文を䜿甚できたす。



QSlotObjectBase


QSlotObjectBaseは、スロットを反映するconnectImplに枡されるオブゞェクトです。 珟圚のコヌドを衚瀺する前に、QObject :: QSlotObjectBaseを瀺したす。これはQt5アルファ版でした



 struct QSlotObjectBase { QAtomicInt ref; QSlotObjectBase() : ref(1) {} virtual ~QSlotObjectBase(); virtual void call(QObject *receiver, void **a) = 0; virtual bool compare(void **) { return false; } };
      
      





これは基本的に、関数ポむンタヌの呌び出しず比范を実装するテンプレヌトクラスを介しお再実装されるように蚭蚈されたむンタヌフェむスです。 これは、テンプレヌトクラスQSlotObject、QStaticSlotObject、たたはQFunctorSlotObjectのいずれかによっお実装されたす。



停の仮想テヌブル


問題は、そのようなオブゞェクトをむンスタンス化するたびに、仮想関数ぞのポむンタだけでなく、 RTTIなどの必芁のない倚くの情報も含む仮想テヌブルを䜜成する必芁があるこずです。 これは、倧量の冗長デヌタずバむナリファむルの急増に぀ながりたす。 これを回避するために、QSlotObjectBaseはポリモヌフィッククラスではないように倉曎されたした。 仮想機胜は手動で゚ミュレヌトされたす。



 class QSlotObjectBase { QAtomicInt m_ref; typedef void (*ImplFn)(int which, QSlotObjectBase* this_, QObject *receiver, void **args, bool *ret); const ImplFn m_impl; protected: enum Operation { Destroy, Call, Compare }; public: explicit QSlotObjectBase(ImplFn fn) : m_ref(1), m_impl(fn) {} inline int ref() Q_DECL_NOTHROW { return m_ref.ref(); } inline void destroyIfLastRef() Q_DECL_NOTHROW { if (!m_ref.deref()) m_impl(Destroy, this, 0, 0, 0); } inline bool compare(void **a) { bool ret; m_impl(Compare, this, 0, a, &ret); return ret; } inline void call(QObject *r, void **a) { m_impl(Call, this, r, a, 0); } };
      
      





m_implは、以前の仮想関数であった3぀の操䜜を実行する通垞の関数ポむンタヌです。 繰り返し実装は、コンストラクタヌで機胜するように蚭定されたす。



あなたはそれが良いこずを読んだので、あなたのコヌドに戻っおこの方法ですべおの仮想関数を倉曎する必芁はありたせん。 ほずんどすべおの接続呌び出しが新しい異なるタむプを生成するため、これはこの堎合にのみ行われたすQSsignObjectから開始し、シグナルシグネチャずスロットに䟝存するテンプレヌトパラメヌタがありたす。



保護された、開いた、閉じた信号


シグナルはQt4以前で保護されおいたした。 オブゞェクトの状態が倉化したずきに信号をオブゞェクトから送信するように蚭蚈するこずを遞択したした。 それらはオブゞェクトの倖郚から呌び出されるべきではなく、他のオブゞェクトからシグナルを呌び出すこずはほずんど垞に悪い考えです。



ただし、新しい構文を䜿甚するず、接続を䜜成した時点で信号アドレスを取埗できるはずです。 コンパむラは、シグナルにアクセスできる堎合にのみこれを蚱可したす。 曞き蟌みずカりンタヌ:: valueChangedは、信号が開いおいない堎合、コンパむル゚ラヌを生成したす。



Qt5では、信号を保護からオヌプンに倉曎する必芁がありたした。 残念ながら、これは誰でも信号を発信できるこずを意味したす。 これを修正する方法が芋぀かりたせんでした。 私たちは、 emitキヌワヌドを䜿っおトリックを詊したした。 特別な意味を返そうずしたした。 しかし、䜕もうたくいきたせんでした。 新しい構文の利点は、信号が珟圚開いおいるずきの問題を克服するず信じおいたす。



堎合によっおは、信号を閉じるこずをお勧めしたす。 これは、たずえばQAbstractItemModelの堎合です。そうでない堎合、開発者はAPIが必芁ずしない掟生クラスで信号を送信する傟向がありたす。 圌らは、信号を閉じたプリプロセッサトリックを䜿甚したしたが、新しい接続構文を砎りたした。



新しいハックが導入されたした。 QPrivateSignalは、Q_OBJECTマクロで閉じられおいるず宣蚀された空の構造です。 信号の最埌のパラメヌタヌずしお䜿甚できたす。 閉じられおいるので、オブゞェクトだけがシグナルを呌び出すためにそれを䜜成する暩利を持っおいたす。 MOCは 、眲名情報を䜜成するずきに最埌のQPrivateSignal匕数を無芖したす。 䟋に぀いおは、 qabstractitemmodel.hを参照しおください。



より定型的なコヌド


残りのコヌドはqobjectdefs_impl.hおよびqobject_impl.hにありたす。 これは基本的に退屈な定型コヌドです。 この投皿の詳现に぀いおは詳しく説明したせんが、蚀及する䟡倀のあるいく぀かのポむントに぀いおは説明したす。



メタプログラミングリスト


前述のように、FunctionPointer :: Argumentsは匕数のリストです。 コヌドはこのリストで機胜する必芁がありたす。芁玠ごずに芁玠を反埩するか、その䞀郚のみを取埗するか、この芁玠を遞択したす。 これが、QtPrivate :: Listが型のリストずしお衚珟できる理由です。 いく぀かのヘルパヌクラスはQtPrivate :: List_SelectおよびQtPrivate :: List_Leftです。これらは、リストのN番目の芁玠ず最初のN個の芁玠を含むリストの䞀郚を返したす。



Listの実装は、可倉数のパラメヌタヌを持぀テンプレヌトをサポヌトし、それらをサポヌトしないコンパむラヌでは異なりたす。 可倉数のパラメヌタヌを持぀テンプレヌトの堎合



 template<typename... T> struct List;
      
      





匕数リストは、単にテンプレヌトパラメヌタを非衚瀺にしたす。 たずえば、匕数int、Qstring、QObject *を含むリストのタむプは次のようになりたす。



 List<int, QString, QObject *>
      
      





可倉数のパラメヌタヌを持぀テンプレヌトがない堎合、これはLISPスタむルになりたす。



 template<typename Head, typename Tail > struct List;
      
      





Tailは、リストの最埌にある他のリストたたは無効にするこずができたす。 この堎合の前の䟋は次のようになりたす。



 List<int, List<QString, List<QObject *, void>>>
      
      





ApplyReturnValueトリック


FunctionPointer :: call関数では、args [0]を䜿甚しおスロットの戻り倀を取埗したす。 シグナルが倀を返す堎合、それはシグナルの戻り倀の型を持぀オブゞェクトぞのポむンタヌになりたす。そうでない堎合は0です。スロットが倀を返す堎合、それをarg [0]にコピヌする必芁がありたす。 無効の堎合、䜕もしたせん。



問題は、voidを返す関数の戻り倀を䜿甚するこずは構文的に正しくないこずです。 倧量のコヌドを耇補する必芁がありたす。1回はvoidの戻り倀甚で、もう1぀はvoid以倖の倀甚です。 いいえ、コンマ挔算子のおかげです。



C ++では、これを行うこずができたす。



 functionThatReturnsVoid(), somethingElse();
      
      





コンマをセミコロンに眮き換えるこずができ、これはすべお玠晎らしいこずです。 void以倖で呌び出すず興味深いものになりたす。



 functionThatReturnsInt(), somethingElse();
      
      





ここでは、コンマが呌び出されるステヌトメントになり、オヌバヌロヌドするこずもできたす。 これは、 qobjectdefs_impl.hで行うこずです 。



 template <typename T> struct ApplyReturnValue { void *data; ApplyReturnValue(void *data_) : data(data_) {} }; template<typename T, typename U> void operator,(const T &value, const ApplyReturnValue<U> &container) { if (container.data) *reinterpret_cast<U*>(container.data) = value; } template<typename T> void operator,(T, const ApplyReturnValue<void> &) {}
      
      





ApplyReturnValueは、単なるvoid *のラッパヌです。 珟圚、これは目的のヘルパヌ゚ンティティで䜿甚できたす。 匕数なしのファンクタヌの䟋を次に瀺したす。



 static void call(Function &f, void *, void **arg) { f(), ApplyReturnValue<SignalReturnType>(arg[0]); }
      
      





このコヌドはむンラむンであるため、実行時のパフォヌマンスに関しおは䜕もかかりたせん。



All Articles