Proper use of QPrivateSignal
-
Recently I encountered a situation where I wanted to encapsulate emitting a signal with a (non signal/slot) method, and I decided I should prevent any code from emitting that signal except via the wrapper method. This led me to examine the QPrivateSignal mechanism (I hadn't really paid attention to it before). I found documentation about QPrivateSignal to be misleading, because there was a general implication that QPrivateSignal being a "private" struct would break client code trying to emit (i.e., call) a signal because it would need to supply a QPrivateSignal argument, and the constructor QPrivateSignal() wouldn't compile. That's true enough, but I got no compiler error with an empty initializer list: i.e.
Q_EMIT done({})
(for signal "done()") would compile (in this context of course I want a compiler error here!). This seems like a trivial way to "break" the encapsulation supposedly available via QPrivateSignal. I found that I could get something like how I expected QPrivateSignal to work by defining the following macro:#define USE_PRIVATE_SIGNALS \ class _QPrivateSignal_ { \ friend struct QPrivateSignal; \ public: explicit _QPrivateSignal_\ (QPrivateSignal = {}){} };
Then I used this _QPrivateSignal_ class instead of QPrivateSignal in my signal declaration:
void done(_QPrivateSignal_);
and the "wrapper" was akin to:void emit_done(){ Q_EMIT done(_QPrivateSignal_{}); }
. This seemed to do the trick in that I couldn't get any call to the signal to compile outside a class member of the relevant QObject subclass. But I'm wondering: am I missing something? Is there a reason the Q_OBJECT macro itself doesn't use this "extra layer" to hide the empty-initializer possibility? Or does my solution have some downside I'm not seeing (there seems to be no problem connecting the signal to lambda handlers)? -
That's interesting. I would have guessed that out of class aggregate initialization of a private class would have broken, but can't find anything to support or deny the suspicion. I also don't see a matching bug report at https://bugreports.qt.io/. Filing a report might net an explanation.
Changing the QPrivateSignal definition in qobjectdefs.h to make the constructor explicit works for me. There's no need to introduce a level of indirection.
-
Recently I encountered a situation where I wanted to encapsulate emitting a signal with a (non signal/slot) method, and I decided I should prevent any code from emitting that signal except via the wrapper method. This led me to examine the QPrivateSignal mechanism (I hadn't really paid attention to it before). I found documentation about QPrivateSignal to be misleading, because there was a general implication that QPrivateSignal being a "private" struct would break client code trying to emit (i.e., call) a signal because it would need to supply a QPrivateSignal argument, and the constructor QPrivateSignal() wouldn't compile. That's true enough, but I got no compiler error with an empty initializer list: i.e.
Q_EMIT done({})
(for signal "done()") would compile (in this context of course I want a compiler error here!). This seems like a trivial way to "break" the encapsulation supposedly available via QPrivateSignal. I found that I could get something like how I expected QPrivateSignal to work by defining the following macro:#define USE_PRIVATE_SIGNALS \ class _QPrivateSignal_ { \ friend struct QPrivateSignal; \ public: explicit _QPrivateSignal_\ (QPrivateSignal = {}){} };
Then I used this _QPrivateSignal_ class instead of QPrivateSignal in my signal declaration:
void done(_QPrivateSignal_);
and the "wrapper" was akin to:void emit_done(){ Q_EMIT done(_QPrivateSignal_{}); }
. This seemed to do the trick in that I couldn't get any call to the signal to compile outside a class member of the relevant QObject subclass. But I'm wondering: am I missing something? Is there a reason the Q_OBJECT macro itself doesn't use this "extra layer" to hide the empty-initializer possibility? Or does my solution have some downside I'm not seeing (there seems to be no problem connecting the signal to lambda handlers)?@Nathaniel Maybe I am wrong, what I understand is that you have to create a private signal for your class which can only be emitted from the class instance itself.
This is pretty simple:
class MyClass: public QObject { Q_OBJECT public: explicit MyClass(QObject *parent = nullptr): QObject(parent) {} public slots: void doWork() { // emit the public signal emit publicSignal(25); // emit the private signal emit privateSignal(25, QPrivateSignal()); } signals: void publicSignal(int data); void privateSignal(int data, QPrivateSignal); };
This way, anyone can call MyClass::publicSignal() but only MyClass instance can call privateSignal(), and each signal can be connected to the same slot:
auto k = new MyClass(); connect(k, &MyClass::publicSignal, this, [](int data) { qDebug() << "received Public:" << data; }); connect(k, &MyClass::privateSignal, this, [](int data) { qDebug() << "received Private:" << data; });
-
@Nathaniel Maybe I am wrong, what I understand is that you have to create a private signal for your class which can only be emitted from the class instance itself.
This is pretty simple:
class MyClass: public QObject { Q_OBJECT public: explicit MyClass(QObject *parent = nullptr): QObject(parent) {} public slots: void doWork() { // emit the public signal emit publicSignal(25); // emit the private signal emit privateSignal(25, QPrivateSignal()); } signals: void publicSignal(int data); void privateSignal(int data, QPrivateSignal); };
This way, anyone can call MyClass::publicSignal() but only MyClass instance can call privateSignal(), and each signal can be connected to the same slot:
auto k = new MyClass(); connect(k, &MyClass::publicSignal, this, [](int data) { qDebug() << "received Public:" << data; }); connect(k, &MyClass::privateSignal, this, [](int data) { qDebug() << "received Private:" << data; });
@KroMignon I believe OP is referring to being able to call a function taking a QPrivateSignal using the aggregate initializer for the corresponding QPrivateSignal.
#include <QTimer> int main(int argc, char *argv[]) { QTimer timer; emit timer.timeout({}); return 0; }
-
@KroMignon I believe OP is referring to being able to call a function taking a QPrivateSignal using the aggregate initializer for the corresponding QPrivateSignal.
#include <QTimer> int main(int argc, char *argv[]) { QTimer timer; emit timer.timeout({}); return 0; }
@jeremy_k said in Proper use of QPrivateSignal:
I believe OP is referring to being able to call a function taking a QPrivateSignal using the aggregate initializer for the corresponding QPrivateSignal.
Sorry, perhaps my english to not good enough, but I don't understand what your are meaning here.
QPrivateSignal()
struct is defined withQ_OBJECT
macro.
This will allow you to mark as signal as 'private', which means it can only be emitted from the class itself, not from outside.
The private signal must use as last parameterQPrivateSignal
, so it can only be called from class itself because no other class can create an instance ofQPrivateSignal
.
This signal is still accessible from outside, but only to be connected with any slot which have same signature (except last parameter of course). -
@jeremy_k said in Proper use of QPrivateSignal:
I believe OP is referring to being able to call a function taking a QPrivateSignal using the aggregate initializer for the corresponding QPrivateSignal.
Sorry, perhaps my english to not good enough, but I don't understand what your are meaning here.
QPrivateSignal()
struct is defined withQ_OBJECT
macro.
This will allow you to mark as signal as 'private', which means it can only be emitted from the class itself, not from outside.
The private signal must use as last parameterQPrivateSignal
, so it can only be called from class itself because no other class can create an instance ofQPrivateSignal
.
This signal is still accessible from outside, but only to be connected with any slot which have same signature (except last parameter of course).@KroMignon said in Proper use of QPrivateSignal:
@jeremy_k said in Proper use of QPrivateSignal:
I believe OP is referring to being able to call a function taking a QPrivateSignal using the aggregate initializer for the corresponding QPrivateSignal.
Sorry, perhaps my english to not good enough, but I don't understand what your are meaning here.
QPrivateSignal()
struct is defined withQ_OBJECT
macro.
This will allow you to mark as signal as 'private', which means it can only be emitted from the class itself, not from outside.
The private signal must use as last parameterQPrivateSignal
, so it can only be called from class itself because no other class can create an instance ofQPrivateSignal
.
This signal is still accessible from outside, but only to be connected with any slot which have same signature (except last parameter of course).Did you try the code above? QTimer::timeout is a private signal, and yet still callable using a clang 13 in C++11 and C++17(+1z) modes.
-
@KroMignon said in Proper use of QPrivateSignal:
@jeremy_k said in Proper use of QPrivateSignal:
I believe OP is referring to being able to call a function taking a QPrivateSignal using the aggregate initializer for the corresponding QPrivateSignal.
Sorry, perhaps my english to not good enough, but I don't understand what your are meaning here.
QPrivateSignal()
struct is defined withQ_OBJECT
macro.
This will allow you to mark as signal as 'private', which means it can only be emitted from the class itself, not from outside.
The private signal must use as last parameterQPrivateSignal
, so it can only be called from class itself because no other class can create an instance ofQPrivateSignal
.
This signal is still accessible from outside, but only to be connected with any slot which have same signature (except last parameter of course).Did you try the code above? QTimer::timeout is a private signal, and yet still callable using a clang 13 in C++11 and C++17(+1z) modes.
-
@jeremy_k said in Proper use of QPrivateSignal:
Did you try the code above? QTimer::timeout is a private signal, and yet still callable using a clang 13 C++.
Sorry, now I understand what you are meaning... sad situation :(
@KroMignon said in Proper use of QPrivateSignal:
@jeremy_k said in Proper use of QPrivateSignal:
Did you try the code above? QTimer::timeout is a private signal, and yet still callable using a clang 13 C++.
Sorry, now I understand what you are meaning... sad situation :(
Or curious. I don't know if this is a failure with Qt, C++, or my understanding.
-