QCoreApplication QMetaCallEvent 圧縮
キューに入れられたすべてのスロット呼び出しはQMetaCallEvent
、ターゲット オブジェクトへの a のポストで終了します。イベントには、送信者オブジェクト、シグナル ID、スロット インデックス、およびパッケージ化された呼び出しパラメーターが含まれます。Qt 5 では、通常、シグナル ID は によって返される値と等しくありませんQMetaObject::signalIndex()
。これは、オブジェクトがシグナル メソッドのみを持ち、他のメソッドを持たないかのように計算されたインデックスです。
目的は、特定のタプル (送信側オブジェクト、送信側シグナル、受信側オブジェクト、受信側スロット) のイベント キューに一意の呼び出しが 1 つだけ存在するように、そのような呼び出しを圧縮することです。
これは、ソース オブジェクトまたはターゲット オブジェクトを変更する必要がなく、最小限のオーバーヘッドを維持しながら、これを行う唯一の適切な方法です。Qtが64ビットポインターアーキテクチャ用に構築されている場合、私の他の回答のイベントループ再帰メソッドには、イベントごとに1kバイトのオーダーで深刻なスタックオーバーヘッドがあります。
1 つ以上のイベントが既にポストされているオブジェクトに新しいイベントがポストされると、イベント キューにアクセスできます。このような場合、 をQCoreApplication::postEvent
呼び出しますQCoreApplication::compressEvent
。compressEvent
最初のイベントがオブジェクトにポストされたときは呼び出されません。このメソッドの再実装ではQMetaCallEvent
、ターゲット オブジェクトにポストされたコンテンツのスロットへの呼び出しをチェックでき、廃止された複製を削除する必要があります。QMetaCallEvent
、QPostEvent
およびの定義を取得するには、プライベート Qt ヘッダーを含める必要がありQPostEventList
ます。
長所: 送信側オブジェクトも受信側オブジェクトも何も意識する必要はありません。シグナルとスロットは、Qt 5 のメソッド ポインター呼び出しを含め、そのままの状態で機能します。Qt 自体は、この方法でイベントを圧縮します。
短所: プライベート Qt ヘッダーを含め、QEvent::posted
フラグを強制的にクリアする必要があります。
フラグをハッキングする代わりに、削除するイベントを別のリストにキューに入れ、ゼロ期間タイマーが起動されたときに呼び出しQEvent::posted
の外で削除することができます。compressEvent
これには、余分なイベント リストのオーバーヘッドがあり、各イベントの削除は、投稿されたイベント リストを反復処理します。
その他のアプローチ
他の方法でそれを行うポイントは、Qt の内部構造を使用しないことです。
L1最初の制限は、私的に定義された の内容にアクセスできないことですQMetaCallEvent
。次のように処理できます。
ターゲットと同じシグネチャの信号とスロットを持つプロキシ オブジェクトは、ソース オブジェクトとターゲット オブジェクトの間に接続できます。
プロキシ オブジェクトで を実行するQMetaCallEvent
と、呼び出しタイプ、呼び出されたスロット ID、および引数を抽出できます。
シグナルスロット接続の代わりに、イベントを明示的にターゲット オブジェクトに送信できます。ターゲット オブジェクトまたはイベント フィルターは、イベントのデータからスロット呼び出しを明示的に再合成する必要があります。
の代わりにカスタムcompressedConnect
実装を使用できますQObject::connect
。これにより、信号とスロットの詳細が完全に明らかになります。queued_activate
プロキシ オブジェクトを使用して、送信側オブジェクトの側で圧縮に適した同等の処理を実行できます。
L2 2 番目の制限はQCoreApplication::compressEvent
、イベント リストが非公開で定義されているため、完全に再実装できないことです。圧縮されているイベントに引き続きアクセスでき、それを削除するかどうかを決定することもできますが、イベント リストを反復処理する方法はありません。したがって:
イベント キューはsendPostedEvents
、内部から再帰的に呼び出すことによって暗黙的にアクセスできますnotify
(したがって、eventFilter()
、event()
またはスロットからも)。QCoreApplication::sendPostedEvents
イベントが 経由で配信されている間は、イベント ループ ミューテックスを保持できない (そして保持しない) ため、これによってデッドロックが発生することはありませんsendEvent
。イベントは次のようにフィルタリングできます。
- 再実装された でグローバルに
QCoreApplication::notify
、
- 、を登録することにより、グローバル
QInternal::EventNotifyCallback
に
- オブジェクトにイベント フィルターをアタッチすることにより、ローカルで、
QObject::event()
ターゲットクラスで再実装することにより、ローカルで明示的に。
重複したイベントは引き続きイベント キューにポストされます。notify
内部からの再帰呼び出しは、sendPostedEvents
かなりのスタック スペースを消費します (64 ビット ポインター アーキテクチャでは 1kb の予算)。
QCoreApplication::removePostedEvents
既に存在するイベントは、新しいイベントをオブジェクトに投稿する前に呼び出すことで削除できます。残念ながら、QCoreApplication::compressEvent
イベント キューのミューテックスが既に保持されているため、この範囲内でこれを行うとデッドロックが発生します。
レシーバー オブジェクトへのポインターを含むカスタム イベント クラスはremovePostedEvents
、コンストラクターで自動的に呼び出すことができます。
などの既存の圧縮されたイベントは、QEvent::Exit
再流用できます。
これらのイベントのセットは実装の詳細であり、変更される可能性があります。QObject
Qt は、レシーバーポインター以外でこれらのイベントを区別しません。実装には、各タプル (イベント タイプ、レシーバー オブジェクト) ごとにプロキシ QObject のオーバーヘッドが必要です。
実装
以下のコードは、Qt 4 と Qt 5 の両方で機能します。後者では、必ずQT += core-private
qmake プロジェクト ファイルに追加して、プライベート Qt ヘッダーが含まれるようにしてください。
Qt 内部ヘッダーを使用しない実装は、他の回答で提供されます。
によって選択された 2 つのイベント削除コード パスがありますif (true)
。有効なコード パスは最新のイベントを保持し、通常は最も理にかなっています。または、最も古いイベントを保持することもできます。これは、無効化されたコード パスが行うことです。

#include <QApplication>
#include <QMap>
#include <QSet>
#include <QMetaMethod>
#include <QMetaObject>
#include <private/qcoreapplication_p.h>
#include <private/qthread_p.h>
#include <private/qobject_p.h>
#include <QWidget>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QSpinBox>
#include <QFormLayout>
// Works on both Qt 4 and Qt 5.
//
// Common Code
/*! Keeps a list of singal indices for one or more meatobject classes.
* The indices are signal indices as given by QMetaCallEvent.signalId.
* On Qt 5, those do *not* match QMetaObject::methodIndex since they
* exclude non-signal methods. */
class SignalList {
Q_DISABLE_COPY(SignalList)
typedef QMap<const QMetaObject *, QSet<int> > T;
T m_data;
/*! Returns a signal index that is can be compared to QMetaCallEvent.signalId. */
static int signalIndex(const QMetaMethod & method) {
Q_ASSERT(method.methodType() == QMetaMethod::Signal);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
int index = -1;
const QMetaObject * mobj = method.enclosingMetaObject();
for (int i = 0; i <= method.methodIndex(); ++i) {
if (mobj->method(i).methodType() != QMetaMethod::Signal) continue;
++ index;
}
return index;
#else
return method.methodIndex();
#endif
}
public:
SignalList() {}
void add(const QMetaMethod & method) {
m_data[method.enclosingMetaObject()].insert(signalIndex(method));
}
void remove(const QMetaMethod & method) {
T::iterator it = m_data.find(method.enclosingMetaObject());
if (it != m_data.end()) {
it->remove(signalIndex(method));
if (it->empty()) m_data.erase(it);
}
}
bool contains(const QMetaObject * metaObject, int signalId) {
T::const_iterator it = m_data.find(metaObject);
return it != m_data.end() && it.value().contains(signalId);
}
};
//
// Implementation Using Event Compression With Access to Private Qt Headers
struct EventHelper : private QEvent {
static void clearPostedFlag(QEvent * ev) {
(&static_cast<EventHelper*>(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted
}
};
template <class Base> class CompressorApplication : public Base {
SignalList m_compressedSignals;
public:
CompressorApplication(int & argc, char ** argv) : Base(argc, argv) {}
void addCompressedSignal(const QMetaMethod & method) { m_compressedSignals.add(method); }
void removeCompressedSignal(const QMetaMethod & method) { m_compressedSignals.remove(method); }
protected:
bool compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) {
if (event->type() != QEvent::MetaCall)
return Base::compressEvent(event, receiver, postedEvents);
QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(event);
if (! m_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) return false;
for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) {
QPostEvent &cur = *it;
if (cur.receiver != receiver || cur.event == 0 || cur.event->type() != event->type())
continue;
QMetaCallEvent *cur_mce = static_cast<QMetaCallEvent*>(cur.event);
if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() ||
cur_mce->id() != mce->id())
continue;
if (true) {
/* Keep The Newest Call */
// We can't merely qSwap the existing posted event with the new one, since QEvent
// keeps track of whether it has been posted. Deletion of a formerly posted event
// takes the posted event list mutex and does a useless search of the posted event
// list upon deletion. We thus clear the QEvent::posted flag before deletion.
EventHelper::clearPostedFlag(cur.event);
delete cur.event;
cur.event = event;
} else {
/* Keep the Oldest Call */
delete event;
}
return true;
}
return false;
}
};
//
// Demo GUI
class Signaller : public QObject {
Q_OBJECT
public:
Q_SIGNAL void emptySignal();
Q_SIGNAL void dataSignal(int);
};
class Widget : public QWidget {
Q_OBJECT
QPlainTextEdit * m_edit;
QSpinBox * m_count;
Signaller m_signaller;
Q_SLOT void emptySlot() {
m_edit->appendPlainText("emptySlot invoked");
}
Q_SLOT void dataSlot(int n) {
m_edit->appendPlainText(QString("dataSlot(%1) invoked").arg(n));
}
Q_SLOT void sendSignals() {
m_edit->appendPlainText(QString("\nEmitting %1 signals").arg(m_count->value()));
for (int i = 0; i < m_count->value(); ++ i) {
emit m_signaller.emptySignal();
emit m_signaller.dataSignal(i + 1);
}
}
public:
Widget(QWidget * parent = 0) : QWidget(parent),
m_edit(new QPlainTextEdit), m_count(new QSpinBox)
{
QFormLayout * l = new QFormLayout(this);
QPushButton * invoke = new QPushButton("Invoke");
m_edit->setReadOnly(true);
m_count->setRange(1, 1000);
l->addRow("Number of slot invocations", m_count);
l->addRow(invoke);
l->addRow(m_edit);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
connect(invoke, &QPushButton::clicked, this, &Widget::sendSignals);
connect(&m_signaller, &Signaller::emptySignal, this, &Widget::emptySlot, Qt::QueuedConnection);
connect(&m_signaller, &Signaller::dataSignal, this, &Widget::dataSlot, Qt::QueuedConnection);
#else
connect(invoke, SIGNAL(clicked()), SLOT(sendSignals()));
connect(&m_signaller, SIGNAL(emptySignal()), SLOT(emptySlot()), Qt::QueuedConnection);
connect(&m_signaller, SIGNAL(dataSignal(int)), SLOT(dataSlot(int)), Qt::QueuedConnection);
#endif
}
};
int main(int argc, char *argv[])
{
CompressorApplication<QApplication> a(argc, argv);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
a.addCompressedSignal(QMetaMethod::fromSignal(&Signaller::emptySignal));
a.addCompressedSignal(QMetaMethod::fromSignal(&Signaller::dataSignal));
#else
a.addCompressedSignal(Signaller::staticMetaObject.method(Signaller::staticMetaObject.indexOfSignal("emptySignal()")));
a.addCompressedSignal(Signaller::staticMetaObject.method(Signaller::staticMetaObject.indexOfSignal("dataSignal(int)")));
#endif
Widget w;
w.show();
return a.exec();
}
#include "main.moc"