I took the (to me) most promising approach from OPs question
QMetaObject::Connection * const connection = new QMetaObject::Connection;
*connection = connect(_textFadeOutAnimation, &QPropertyAnimation::finished, [this, text, connection](){
QObject::disconnect(*connection);
delete connection;
});
and thought about how to wrap it into a function. Actually, it has to be a template function to make it usable for any Qt signal:
template <typename Sender, typename Emitter, typename Slot, typename... Args>
QMetaObject::Connection connectOneShot(
Sender *pSender, void (Emitter::*pSignal)(Args ...args), Slot slot)
{
QMetaObject::Connection *pConnection = new QMetaObject::Connection;
*pConnection
= QObject::connect(pSender, pSignal,
[pConnection, slot](Args... args)
{
QObject::disconnect(*pConnection);
delete pConnection;
slot(args...);
});
return *pConnection;
}
I tried this in an MCVE:
#include <functional>
#include <QtWidgets>
template <typename Sender, typename Emitter, typename Slot, typename... Args>
QMetaObject::Connection connectOneShot(Sender *pSender, void (Emitter::*pSignal)(Args ...args), Slot slot)
{
QMetaObject::Connection *pConnection = new QMetaObject::Connection;
*pConnection
= QObject::connect(pSender, pSignal,
[pConnection, slot](Args... args)
{
QObject::disconnect(*pConnection);
delete pConnection;
slot(args...);
});
return *pConnection;
}
template <typename Sender, typename Emitter,
typename Receiver, typename Slot, typename... Args>
QMetaObject::Connection connectOneShot(
Sender *pSender, void (Emitter::*pSignal)(Args ...args),
Receiver *pRecv, Slot slot)
{
QMetaObject::Connection *pConnection = new QMetaObject::Connection;
*pConnection
= QObject::connect(pSender, pSignal,
[pConnection, pRecv, slot](Args... args)
{
QObject::disconnect(*pConnection);
delete pConnection;
(pRecv->*slot)(args...);
});
return *pConnection;
}
void onBtnClicked(bool)
{
static int i = 0;
qDebug() << "onBtnClicked() called:" << ++i;
}
struct PushButton: public QPushButton {
int i = 0;
using QPushButton::QPushButton;
virtual ~PushButton() = default;
void onClicked(bool)
{
++i;
qDebug() << this << "PushButton::onClicked() called:" << i;
setText(QString("Clicked %1.").arg(i));
}
};
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup user interface
QWidget qWinMain;
QGridLayout qGrid;
qGrid.addWidget(new QLabel("Multi Shot"), 0, 1);
qGrid.addWidget(new QLabel("One Shot"), 0, 2);
auto addRow
= [](
QGridLayout &qGrid, const QString &qText,
QWidget &qWidgetMShot, QWidget &qWidgetOneShot)
{
const int i = qGrid.rowCount();
qGrid.addWidget(new QLabel(qText), i, 0);
qGrid.addWidget(&qWidgetMShot, i, 1);
qGrid.addWidget(&qWidgetOneShot, i, 2);
};
QPushButton qBtnMShotFunc("Click me!");
QPushButton qBtnOneShotFunc("Click me!");
addRow(qGrid, "Function:", qBtnMShotFunc, qBtnOneShotFunc);
PushButton qBtnMShotMemFunc("Click me!");
PushButton qBtnOneShotMemFunc("Click me!");
addRow(qGrid, "Member Function:", qBtnMShotMemFunc, qBtnOneShotMemFunc);
QPushButton qBtnMShotLambda("Click me!");
QPushButton qBtnOneShotLambda("Click me!");
addRow(qGrid, "Lambda:", qBtnMShotLambda, qBtnOneShotLambda);
QLineEdit qEditMShot("Edit me!");
QLineEdit qEditOneShot("Edit me!");
addRow(qGrid, "Lambda:", qEditMShot, qEditOneShot);
qWinMain.setLayout(&qGrid);
qWinMain.show();
// install signal handlers
QObject::connect(&qBtnMShotFunc, &QPushButton::clicked,
&onBtnClicked);
connectOneShot(&qBtnOneShotFunc, &QPushButton::clicked,
&onBtnClicked);
QObject::connect(&qBtnMShotMemFunc, &QPushButton::clicked,
&qBtnMShotMemFunc, &PushButton::onClicked);
connectOneShot(&qBtnOneShotMemFunc, &QPushButton::clicked,
&qBtnOneShotMemFunc, &PushButton::onClicked);
QObject::connect(&qBtnMShotLambda, &QPushButton::clicked,
[&](bool) {
qDebug() << "[&](bool) qBtnMShotLambda called.";
static int i = 0;
qBtnMShotLambda.setText(QString("Clicked %1.").arg(++i));
});
connectOneShot(&qBtnOneShotLambda, &QPushButton::clicked,
[&](bool) {
qDebug() << "[&](bool) for qBtnOneShotLambda called.";
static int i = 0;
qBtnOneShotLambda.setText(QString("Clicked %1.").arg(++i));
});
QObject::connect(&qEditMShot, &QLineEdit::editingFinished,
[&]() {
qDebug() << "[&]() for qEditMShot called. Input:" << qEditMShot.text();
qEditMShot.setText("Well done.");
});
connectOneShot(&qEditOneShot, &QLineEdit::editingFinished,
[&]() {
qDebug() << "[&]() for qEditOneShot called. Input:" << qEditOneShot.text();
qEditOneShot.setText("No more input accepted.");
qEditOneShot.setEnabled(false);
});
// run
return app.exec();
}
The corresponding Qt project:
SOURCES = testQSignalOneShot.cc
QT += widgets
Tested in VS2017 on Windows 10:

Tested in with g++ in cygwin64 (X11):
$ qmake-qt5 testQSignalOneShot.pro
$ make && ./testQSignalOneShot
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQSignalOneShot.o testQSignalOneShot.cc
g++ -o testQSignalOneShot.exe testQSignalOneShot.o -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
Qt Version: 5.9.4
onBtnClicked() called: 1
onBtnClicked() called: 2
onBtnClicked() called: 3
QPushButton(0xffffcb60) PushButton::onClicked() called: 1
QPushButton(0xffffcb60) PushButton::onClicked() called: 2
QPushButton(0xffffcba0) PushButton::onClicked() called: 1
[&](bool) qBtnMShotLambda called.
[&](bool) qBtnMShotLambda called.
[&](bool) for qBtnOneShotLambda called.
[&]() for qEditMShot called. Input: "abc123"
[&]() for qEditMShot called. Input: "def456"
[&]() for qEditMShot called. Input: "Well done."
[&]() for qEditOneShot called. Input: "abc456"

Note:
I must admit that this solution has a little flaw. If a one-shot-connection is not "fired" but disconnected (e.g. because sender object is deleted) then the QMetaObject::Connection
is not deleted and becomes a memory leak. I pondered a while how to solve this without having a good idea. Finally, I decided to sent this as is. So, please, take this with a grain of salt – it's not yet "production-ready". At least, it shows the idea.
Finally, I solved the issue with the memory-leak.
The crux with the connection is that it has to be passed to the inner "trampoline" lambda.
However, passing the connection by value would ensure proper storage management but at this point it's not yet initialized.
Passing it by reference could solve this issue but this would capture a reference to a local variable (fatal).
Hence, the solution with new QMetaObject::Connection
seems the only working as the pointer can be passed by value but the instance can be updated afterwards. Due to allocation with new
, the application has control over life-time of QMetaObject::Connection
instance.
But what if the signal is not emitted. My solution: Otherwise, the sender object might be responsible to delete it. This can be achieved in Qt by "attaching" a QObject
to another (setting the latter as parent of the former).
Based on this, an improved solution, where I stored the QMetaObject::Connection
in a wrapper derived from QObject
:
#include <functional>
#include <QtWidgets>
struct ConnectionWrapper: QObject {
ConnectionWrapper(QObject *pQParent): QObject(pQParent) { }
ConnectionWrapper(const ConnectionWrapper&) = delete;
ConnectionWrapper& operator=(const ConnectionWrapper&) = delete;
virtual ~ConnectionWrapper()
{
qDebug() << "ConnectionWrapper::~ConnectionWrapper()";
}
QMetaObject::Connection connection;
};
template <typename Sender, typename Emitter, typename Slot, typename... Args>
QMetaObject::Connection connectOneShot(Sender *pSender, void (Emitter::*pSignal)(Args ...args), Slot slot)
{
ConnectionWrapper *pConn = new ConnectionWrapper(pSender);
pConn->connection
= QObject::connect(pSender, pSignal,
[pConn, slot](Args... args)
{
QObject::disconnect(pConn->connection);
delete pConn;
slot(args...);
});
return pConn->connection;
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup user interface
QWidget qWinMain;
QGridLayout qGrid;
auto addRow
= [](QGridLayout &qGrid, const QString &qText, QWidget &qWidgetMShot, QWidget &qWidgetOneShot)
{
const int i = qGrid.rowCount();
qGrid.addWidget(new QLabel(qText), i, 0);
qGrid.addWidget(&qWidgetMShot, i, 1);
qGrid.addWidget(&qWidgetOneShot, i, 2);
};
QPushButton qBtn("One Shot");
QPushButton qBtnDisconnect("Disconnect");
addRow(qGrid, "Disconnect Test:", qBtnDisconnect, qBtn);
qWinMain.setLayout(&qGrid);
qWinMain.show();
// install signal handlers
QMetaObject::Connection connectionBtn
= connectOneShot(&qBtn, &QPushButton::clicked,
[&](bool) {
qDebug() << "[&](bool) for qBtn called.";
static int i = 0;
qBtn.setText(QString("Clicked %1.").arg(++i));
});
QObject::connect(&qBtnDisconnect, &QPushButton::clicked,
[&](bool) {
QObject::disconnect(connectionBtn);
qDebug() << "qBtn disconnected.";
});
// run
return app.exec();
}
The corresponding Qt project:
SOURCES = testQSignalOneShot2.cc
QT += widgets
I tested again in VS2017 (Windows 10 "native") and on cygwin64 with X11. Below the session in the latter:
$ qmake-qt5 testQSignalOneShot2.pro
$ make && ./testQSignalOneShot2
/usr/bin/qmake-qt5 -o Makefile testQSignalOneShot2.pro
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQSignalOneShot2.o testQSignalOneShot2.cc
g++ -o testQSignalOneShot2.exe testQSignalOneShot2.o -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
Qt Version: 5.9.4
ConnectionWrapper::~ConnectionWrapper()
[&](bool) for qBtn called.
qBtn disconnected.
In this case, I first pressed One Shot and second Disconnect. Tested again for the opposite order:
$ make && ./testQSignalOneShot2
make: Nothing to be done for 'first'.
Qt Version: 5.9.4
qBtn disconnected.
ConnectionWrapper::~ConnectionWrapper()
In this case, the output of ConnectionWrapper::~ConnectionWrapper()
didn't appear before I exited application. (Makes sense – the sender qBtn
was deleted when scope of main()
was left.)
