Background
I believe I have come up with a solution to the infamous problem of template signals and slot within Qt. For starters, I have defined an empty base Message
class, whose sole purpose is to be inherited from and create concrete TMessage
implementations. In Qt, while it is possible to connect a signal to a functor (as opposed to a Qt slot), a signal cannot be a template function, so my solution was to model the relation of the Message
class as well, creating an abstract Messenger
class, with a void signal_Message(Message)
signal and a template <typename T> void slot_T(TMessage<T>)
. I then came across this question and this question, and realized this was a much more maintanable solution, and I created essentially what is a bag of Messenger
s. When using an unsupported type, the even code fails at compile time with a nice, readable error message: invalid initialization of reference of type Messenger<double>& from expression of type MessengerBag<int, char>
Problem
Consider we use 6 distinct types, int
, float
, char
, double
, some enum
, and a struct
. This yields a sizeof
96 bytes! However, given that the desired usage is for places with upwards of 50 user defined types, each with their own separately maintained containers of signals and slots, it looks as though my solution can perform the same task with around the same overhead, but next to none of the maintenance costs. Aside from the obvious benefits of no longer having to maintain 24 separate functions, possibly even more in the case of convoluted inheritance schemes, is there a downside to this approach? After doing some initial testing I found that The MessengerBag
is essentially the opposite of a diamond pattern. Each destructor is called accordingly, so I see practically no disadvantages when compared to a composite structure, which would require another level of (what I consider to be needless and obfuscating) indirection. Qt essentially despises virtual inheritance, and disallows it in a class that directly inherits QObject: as such I cannot make Mock virtual as I would have preferred.
Example
// Message.h
struct Message {};
template <typename T> struct TMessage {
T t
};
// Messenger.h
struct Messenger : public QObject {
Q_OBJECT
public:
template <typename T> void slot_doSomethingWithMsg(const Message& msg){
const auto& tmsg = static_cast<const TMessage<T>&>(msg);
qDebug() << tmsg.t;
// do something else... this is just example usage
}
signals:
void signal_sendMsg(const BaseMsg& msg);
// MessengerBag.h
template <typename T, typename... Args>
struct MessengerBag : TMessenger<T>, MessengerBag<Args...>{};
template <typename T> struct MessengerBag<T> : TMessenger<T>{};
// Manager.h
MessengerBag<int, char> messengerBag;
Manager(){
QObject::connect(static_cast<TMock<int>*>(messengerBag),
&Mock::signal_sendMsg,
static_cast<TMock<int>*>(messengerBag),
&Mock::slot_doSomethingWithMsg<int>);
QObject::connect(static_cast<TMock<char>*>(messengerBag),
&Mock::signal_sendMsg,
static_cast<TMock<char>*>(messengerBag),
&Mock::slot_doSomethingWithMsg<char>);
template <typename T> void slot_sendMsg(const TMessage<T>& msg){
TMessenger<T>& messenger = messengerBag;
messenger.signal_sendMsg(msg);
}
// main.cpp
auto* manager = new Manager();
TMessage<int> imsg{5};
TMessage<char> cmsg{'f'};
manager->slot_sendMsg(imsg);
manager->slot_sendMsg(cmsg);
As expected, the above example prints 5 and 'f', thus subverting the issue with template signals and slots. However, I am having a little trouble seeing any possible issues in the future. One issue is of course that QObject
is inaccessible because which QObject
remains unknown, which a compositional solution could potentially mediate (make TMessenger
delegate to Messenger
, and MessengerBag
inherit QObject
in empty Args
specialization. I hesitate to commit to this solution without first understanding its possible shortcomings. Also, feel free to leave comments on the code itself.
Aucun commentaire:
Enregistrer un commentaire