QTimer: add convenience singleShot methods for functors

This brings QTimer::singleShot on par with QObject::connect in
terms of the new Qt5 syntax. With this patch, it is now possible
to connect singleShot to a member pointer, a static function
pointer and a functor (with or without a context object).

The short code path for 0 msec is not yet implemented - it will
require further modifications to QMetaObject before it will be.

An additional SFINAE on the new singleShot overloads had to be
implemented to prevent tricking the compiler into believing
const char * might be a function pointer.

[ChangeLog][QtCore][QTimer] Implemented new style connect syntax,
including functors, in QTimer::singleShot

Task-number: QTBUG-26406
Change-Id: I31b2fa2c8369648030ec80b12e3ae10b92eb28b4
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
This commit is contained in:
Dario Freddi 2013-09-21 21:01:58 +02:00 committed by The Qt Project
parent 161d98a61a
commit e2df05f120
3 changed files with 344 additions and 4 deletions

View File

@ -260,26 +260,50 @@ class QSingleShotTimer : public QObject
{
Q_OBJECT
int timerId;
bool hasValidReceiver;
QPointer<const QObject> receiver;
QtPrivate::QSlotObjectBase *slotObj;
public:
~QSingleShotTimer();
QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char * m);
QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj);
Q_SIGNALS:
void timeout();
protected:
void timerEvent(QTimerEvent *);
};
QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member)
: QObject(QAbstractEventDispatcher::instance())
QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char *member)
: QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(true), slotObj(0)
{
connect(this, SIGNAL(timeout()), receiver, member);
timerId = startTimer(msec, timerType);
connect(this, SIGNAL(timeout()), r, member);
}
QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj)
: QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(r), receiver(r), slotObj(slotObj)
{
timerId = startTimer(msec, timerType);
if (r && thread() != r->thread()) {
// We need the invocation to happen in the receiver object's thread.
// So, move QSingleShotTimer to the correct thread. Before that occurs, we
// shall remove the parent from the object.
setParent(0);
moveToThread(r->thread());
// Given we're also parentless now, we should take defence against leaks
// in case the application quits before we expire.
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
}
}
QSingleShotTimer::~QSingleShotTimer()
{
if (timerId > 0)
killTimer(timerId);
if (slotObj)
slotObj->destroyIfLastRef();
}
void QSingleShotTimer::timerEvent(QTimerEvent *)
@ -289,7 +313,18 @@ void QSingleShotTimer::timerEvent(QTimerEvent *)
if (timerId > 0)
killTimer(timerId);
timerId = -1;
emit timeout();
if (slotObj) {
// If the receiver was destroyed, skip this part
if (Q_LIKELY(!receiver.isNull() || !hasValidReceiver)) {
// We allocate only the return type - we previously checked the function had
// no arguments.
void *args[1] = { 0 };
slotObj->call(const_cast<QObject*>(receiver.data()), args);
}
} else {
emit timeout();
}
// we would like to use delete later here, but it feels like a
// waste to post a new event to handle this event, so we just unset the flag
@ -297,6 +332,25 @@ void QSingleShotTimer::timerEvent(QTimerEvent *)
qDeleteInEventHandler(this);
}
/*!
\internal
Implementation of the template version of singleShot
\a msec is the timer interval
\a timerType is the timer type
\a receiver is the receiver object, can be null. In such a case, it will be the same
as the final sender class.
\a slot a pointer only used when using Qt::UniqueConnection
\a slotObj the slot object
*/
void QTimer::singleShotImpl(int msec, Qt::TimerType timerType,
const QObject *receiver,
QtPrivate::QSlotObjectBase *slotObj)
{
new QSingleShotTimer(msec, timerType, receiver, slotObj);
}
/*!
\reentrant
This static function calls a slot after a given time interval.
@ -357,6 +411,129 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv
}
}
/*!\fn void QTimer::singleShot(int msec, const QObject *receiver, PointerToMemberFunction method)
\since 5.4
\overload
\reentrant
This static function calls a member function of a QObject after a given time interval.
It is very convenient to use this function because you do not need
to bother with a \l{QObject::timerEvent()}{timerEvent} or
create a local QTimer object.
The \a receiver is the receiving object and the \a method is the member function. The
time interval is \a msec milliseconds.
If \a receiver is destroyed before the interval occurs, the method will not be called.
The function will be run in the thread of \a receiver. The receiver's thread must have
a running Qt event loop.
\sa start()
*/
/*!\fn void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method)
\since 5.4
\overload
\reentrant
This static function calls a member function of a QObject after a given time interval.
It is very convenient to use this function because you do not need
to bother with a \l{QObject::timerEvent()}{timerEvent} or
create a local QTimer object.
The \a receiver is the receiving object and the \a method is the member function. The
time interval is \a msec milliseconds. The \a timerType affects the
accuracy of the timer.
If \a receiver is destroyed before the interval occurs, the method will not be called.
The function will be run in the thread of \a receiver. The receiver's thread must have
a running Qt event loop.
\sa start()
*/
/*!\fn void QTimer::singleShot(int msec, Functor functor)
\since 5.4
\overload
\reentrant
This static function calls \a functor after a given time interval.
It is very convenient to use this function because you do not need
to bother with a \l{QObject::timerEvent()}{timerEvent} or
create a local QTimer object.
The time interval is \a msec milliseconds.
\sa start()
*/
/*!\fn void QTimer::singleShot(int msec, Qt::TimerType timerType, Functor functor)
\since 5.4
\overload
\reentrant
This static function calls \a functor after a given time interval.
It is very convenient to use this function because you do not need
to bother with a \l{QObject::timerEvent()}{timerEvent} or
create a local QTimer object.
The time interval is \a msec milliseconds. The \a timerType affects the
accuracy of the timer.
\sa start()
*/
/*!\fn void QTimer::singleShot(int msec, const QObject *context, Functor functor)
\since 5.4
\overload
\reentrant
This static function calls \a functor after a given time interval.
It is very convenient to use this function because you do not need
to bother with a \l{QObject::timerEvent()}{timerEvent} or
create a local QTimer object.
The time interval is \a msec milliseconds.
If \a context is destroyed before the interval occurs, the method will not be called.
The function will be run in the thread of \a context. The context's thread must have
a running Qt event loop.
\sa start()
*/
/*!\fn void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor)
\since 5.4
\overload
\reentrant
This static function calls \a functor after a given time interval.
It is very convenient to use this function because you do not need
to bother with a \l{QObject::timerEvent()}{timerEvent} or
create a local QTimer object.
The time interval is \a msec milliseconds. The \a timerType affects the
accuracy of the timer.
If \a context is destroyed before the interval occurs, the method will not be called.
The function will be run in the thread of \a context. The context's thread must have
a running Qt event loop.
\sa start()
*/
/*!
\property QTimer::singleShot
\brief whether the timer is a single-shot timer

View File

@ -81,6 +81,67 @@ public:
static void singleShot(int msec, const QObject *receiver, const char *member);
static void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member);
#ifdef Q_QDOC
static void singleShot(int msec, const QObject *receiver, PointerToMemberFunction method);
static void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method);
static void singleShot(int msec, Functor functor);
static void singleShot(int msec, Qt::TimerType timerType, Functor functor);
static void singleShot(int msec, const QObject *context, Functor functor);
static void singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor);
#else
// singleShot to a QObject slot
template <typename Func1>
static inline void singleShot(int msec, const typename QtPrivate::FunctionPointer<Func1>::Object *receiver, Func1 slot)
{
singleShot(msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, receiver, slot);
}
template <typename Func1>
static inline void singleShot(int msec, Qt::TimerType timerType, const typename QtPrivate::FunctionPointer<Func1>::Object *receiver,
Func1 slot)
{
typedef QtPrivate::FunctionPointer<Func1> SlotType;
//compilation error if the slot has arguments.
Q_STATIC_ASSERT_X(int(SlotType::ArgumentCount) == 0,
"The slot must not have any arguments.");
singleShotImpl(msec, timerType, receiver,
new QtPrivate::QSlotObject<Func1, typename SlotType::Arguments, void>(slot));
}
// singleShot to a functor or function pointer (without context)
template <typename Func1>
static inline void singleShot(int msec, Func1 slot)
{
singleShot(msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, Q_NULLPTR, slot);
}
template <typename Func1>
static inline void singleShot(int msec, Qt::TimerType timerType, Func1 slot)
{
singleShot(msec, timerType, Q_NULLPTR, slot);
}
// singleShot to a functor or function pointer (with context)
template <typename Func1>
static inline typename QtPrivate::QEnableIf<!QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction &&
!QtPrivate::is_same<const char*, Func1>::value, void>::Type
singleShot(int msec, QObject *context, Func1 slot)
{
singleShot(msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, context, slot);
}
template <typename Func1>
static inline typename QtPrivate::QEnableIf<!QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction &&
!QtPrivate::is_same<const char*, Func1>::value, void>::Type
singleShot(int msec, Qt::TimerType timerType, QObject *context, Func1 slot)
{
//compilation error if the slot has arguments.
typedef QtPrivate::FunctionPointer<Func1> SlotType;
Q_STATIC_ASSERT_X(int(SlotType::ArgumentCount) <= 0, "The slot must not have any arguments.");
singleShotImpl(msec, timerType, context,
new QtPrivate::QFunctorSlotObject<Func1, 0,
typename QtPrivate::List_Left<void, 0>::Value, void>(slot));
}
#endif
public Q_SLOTS:
void start(int msec);
@ -103,6 +164,9 @@ private:
inline int startTimer(int){ return -1;}
inline void killTimer(int){}
static void singleShotImpl(int msec, Qt::TimerType timerType,
const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj);
int id, inter, del;
uint single : 1;
uint nulltimer : 1;

View File

@ -77,6 +77,7 @@ private slots:
void cancelLongTimer();
void singleShotStaticFunctionZeroTimeout();
void recurseOnTimeoutAndStopTimer();
void singleShotToFunctors();
void dontBlockEvents();
void postedEventsShouldNotStarveTimers();
@ -586,6 +587,14 @@ void tst_QTimer::singleShotStaticFunctionZeroTimeout()
QCOMPARE(helper.count, 1);
QTest::qWait(500);
QCOMPARE(helper.count, 1);
TimerHelper nhelper;
QTimer::singleShot(0, &nhelper, &TimerHelper::timeout);
QCoreApplication::processEvents();
QCOMPARE(nhelper.count, 1);
QCoreApplication::processEvents();
QCOMPARE(nhelper.count, 1);
}
class RecursOnTimeoutAndStopTimerTimer : public QObject
@ -631,6 +640,96 @@ void tst_QTimer::recurseOnTimeoutAndStopTimer()
QVERIFY(!t.two->isActive());
}
struct CountedStruct
{
CountedStruct(int *count, QThread *t = Q_NULLPTR) : count(count), thread(t) { }
~CountedStruct() { }
void operator()() const { ++(*count); if (thread) QCOMPARE(QThread::currentThread(), thread); }
int *count;
QThread *thread;
};
static QEventLoop _e;
static QThread *_t = Q_NULLPTR;
class StaticEventLoop
{
public:
static void quitEventLoop() { _e.quit(); if (_t) QCOMPARE(QThread::currentThread(), _t); }
};
void tst_QTimer::singleShotToFunctors()
{
int count = 0;
QEventLoop e;
QTimer::singleShot(0, CountedStruct(&count));
QCoreApplication::processEvents();
QCOMPARE(count, 1);
QTimer::singleShot(0, &StaticEventLoop::quitEventLoop);
QCOMPARE(_e.exec(), 0);
QThread t1;
QObject c1;
c1.moveToThread(&t1);
QObject::connect(&t1, SIGNAL(started()), &e, SLOT(quit()));
t1.start();
QCOMPARE(e.exec(), 0);
QTimer::singleShot(0, &c1, CountedStruct(&count, &t1));
QTest::qWait(500);
QCOMPARE(count, 2);
t1.quit();
t1.wait();
_t = new QThread;
QObject c2;
c2.moveToThread(_t);
QObject::connect(_t, SIGNAL(started()), &e, SLOT(quit()));
_t->start();
QCOMPARE(e.exec(), 0);
QTimer::singleShot(0, &c2, &StaticEventLoop::quitEventLoop);
QCOMPARE(_e.exec(), 0);
_t->quit();
_t->wait();
_t->deleteLater();
_t = Q_NULLPTR;
{
QObject c3;
QTimer::singleShot(500, &c3, CountedStruct(&count));
}
QTest::qWait(800);
QCOMPARE(count, 2);
#if defined(Q_COMPILER_LAMBDA)
QTimer::singleShot(0, [&count] { ++count; });
QCoreApplication::processEvents();
QCOMPARE(count, 3);
QObject context;
QThread thread;
context.moveToThread(&thread);
QObject::connect(&thread, SIGNAL(started()), &e, SLOT(quit()));
thread.start();
QCOMPARE(e.exec(), 0);
QTimer::singleShot(0, &context, [&count, &thread] { ++count; QCOMPARE(QThread::currentThread(), &thread); });
QTest::qWait(500);
QCOMPARE(count, 4);
thread.quit();
thread.wait();
#endif
}
class DontBlockEvents : public QObject