Make QGenericArgument/Q_ARG from QVariant - c++

I wrote an interface for systemd-timedated:
#include <QtDBus>
#include <dbus/dbus.h>
Q_DECLARE_LOGGING_CATEGORY(timeDateInterfaceCategory)
#define TIMEDATE_DBUS_SERVICE "org.freedesktop.timedate1"
#define TIMEDATE_DBUS_PATH "/org/freedesktop/timedate1"
#define TIMEDATE_DBUS_INTERFACE "org.freedesktop.timedate1"
class TimeDateInterface
: public QDBusAbstractInterface
{
Q_OBJECT
Q_PROPERTY(bool CanNTP MEMBER CanNTP NOTIFY CanNTPChanged)
Q_PROPERTY(bool LocalRTC MEMBER LocalRTC NOTIFY LocalRTCChanged)
Q_PROPERTY(bool NTP MEMBER NTP NOTIFY NTPChanged)
Q_PROPERTY(bool NTPSynchronized MEMBER NTPSynchronized NOTIFY NTPSynchronizedChanged)
Q_PROPERTY(qulonglong RTCTimeUSec MEMBER RTCTimeUSec NOTIFY RTCTimeUSecChanged)
Q_PROPERTY(qulonglong TimeUSec MEMBER TimeUSec NOTIFY TimeUSecChanged)
Q_PROPERTY(QString Timezone MEMBER Timezone NOTIFY TimezoneChanged)
public :
explicit
TimeDateInterface(QObject * const parent = Q_NULLPTR)
: QDBusAbstractInterface{{TIMEDATE_DBUS_SERVICE}, {TIMEDATE_DBUS_PATH}, TIMEDATE_DBUS_INTERFACE,
QDBusConnection::systemBus(),
parent}
{
qDBusRegisterMetaType< QVariantMap >();
if (!isValid()) {
qCCritical(timeDateInterfaceCategory).noquote()
<< tr("Unable to create interface %1: %2")
.arg(service(), lastError().message());
return;
}
if (!connection().connect({service()}, path(), {DBUS_INTERFACE_PROPERTIES}, {"PropertiesChanged"},
//QString::fromLatin1(QMetaObject::normalizedSignature("PropertiesChanged(QString,QVariantMap,QStringList)")),
this, SLOT(propertiesChanged(QString, QVariantMap, QStringList)))) {
Q_ASSERT(false);
}
}
public Q_SLOTS :
Q_SCRIPTABLE
void SetLocalRTC(bool localRtc, bool fixSystem, bool userInteraction)
{
const auto message = call(QDBus::BlockWithGui, {"SetLocalRTC"},
QVariant::fromValue(localRtc),
QVariant::fromValue(fixSystem),
QVariant::fromValue(userInteraction));
QDBusPendingReply<> pendingReply = message;
Q_ASSERT(pendingReply.isFinished());
if (pendingReply.isError()) {
qCWarning(timeDateInterfaceCategory).noquote()
<< tr("Asynchronous call finished with error: %1")
.arg(pendingReply.error().message());
return;
}
}
Q_SCRIPTABLE
void SetNTP(bool useNtp, bool userInteraction)
{
const auto message = call(QDBus::BlockWithGui, {"SetNTP"},
QVariant::fromValue(useNtp),
QVariant::fromValue(userInteraction));
QDBusPendingReply<> pendingReply = message;
Q_ASSERT(pendingReply.isFinished());
if (pendingReply.isError()) {
qCWarning(timeDateInterfaceCategory).noquote()
<< tr("Asynchronous call finished with error: %1")
.arg(pendingReply.error().message());
return;
}
}
Q_SCRIPTABLE
void SetTime(qlonglong usecUtc, bool relative, bool userInteraction)
{
const auto message = call(QDBus::BlockWithGui, {"SetTime"},
QVariant::fromValue(usecUtc),
QVariant::fromValue(relative),
QVariant::fromValue(userInteraction));
QDBusPendingReply<> pendingReply = message;
Q_ASSERT(pendingReply.isFinished());
if (pendingReply.isError()) {
qCWarning(timeDateInterfaceCategory).noquote()
<< tr("Asynchronous call finished with error: %1")
.arg(pendingReply.error().message());
return;
}
}
Q_SCRIPTABLE
void SetTimezone(QString timezone, bool userInteraction)
{
const auto message = call(QDBus::BlockWithGui, {"SetTimezone"},
QVariant::fromValue(timezone),
QVariant::fromValue(userInteraction));
QDBusPendingReply<> pendingReply = message;
Q_ASSERT(pendingReply.isFinished());
if (pendingReply.isError()) {
qCWarning(timeDateInterfaceCategory).noquote()
<< tr("Asynchronous call finished with error: %1")
.arg(pendingReply.error().message());
return;
}
}
private Q_SLOTS :
void propertyChanged(QString const & propertyName)
{
const auto signature = QStringLiteral("%1Changed()").arg(propertyName);
const int signalIndex = staticMetaObject.indexOfSignal(QMetaObject::normalizedSignature(qUtf8Printable(signature)).constData());
if (signalIndex < 0) {
qCCritical(timeDateInterfaceCategory).noquote()
<< tr("There is no signal with %1 signature")
.arg(signature);
return;
}
const auto signal = staticMetaObject.method(signalIndex);
if (!signal.invoke(this, Qt::DirectConnection)) {
qCCritical(timeDateInterfaceCategory).noquote()
<< tr("Unable to emit %1 signal for %2 property")
.arg(signature, propertyName);
}
}
void propertiesChanged(QString interfaceName, QVariantMap changedProperties, QStringList invalidatedProperties)
{
if (interfaceName != interface()) {
return;
}
QMapIterator< QString, QVariant > i{changedProperties};
while (i.hasNext()) {
propertyChanged(i.next().key());
}
for (QString const & invalidatedProperty : invalidatedProperties) {
propertyChanged(invalidatedProperty);
}
}
Q_SIGNALS :
void CanNTPChanged();
void LocalRTCChanged();
void NTPChanged();
void NTPSynchronizedChanged();
void RTCTimeUSecChanged();
void TimeUSecChanged();
void TimezoneChanged();
private :
bool CanNTP;
bool LocalRTC;
bool NTP;
bool NTPSynchronized;
qulonglong RTCTimeUSec;
qulonglong TimeUSec;
QString Timezone;
};
On my system qdbus --system org.freedesktop.timedate1 /org/freedesktop/timedate1 gives:
method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()
method QString org.freedesktop.DBus.Introspectable.Introspect()
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface, QVariantMap changed_properties, QStringList invalidated_properties)
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface, QString property)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface)
method void org.freedesktop.DBus.Properties.Set(QString interface, QString property, QDBusVariant value)
property read bool org.freedesktop.timedate1.CanNTP
property read bool org.freedesktop.timedate1.LocalRTC
property read bool org.freedesktop.timedate1.NTP
property read bool org.freedesktop.timedate1.NTPSynchronized
property read qulonglong org.freedesktop.timedate1.RTCTimeUSec
property read qulonglong org.freedesktop.timedate1.TimeUSec
property read QString org.freedesktop.timedate1.Timezone
method void org.freedesktop.timedate1.SetLocalRTC(bool, bool, bool)
method void org.freedesktop.timedate1.SetNTP(bool, bool)
method void org.freedesktop.timedate1.SetTime(qlonglong, bool, bool)
method void org.freedesktop.timedate1.SetTimezone(QString, bool)
I consider it is a natural to use PropertiesChanged D-Bus signal to emit somePropertyChanged signals per property. It just connected to propertiesChanged dispatcher slot. I need it to emit somePropertyChanged signals from QML-side singletone (which, in turn, just translates D-Bus properties names to QML properties names (decapitalizing, conversion from usecs since Epoch to QDateTime etc)).
There is requirements in Qt Property System to NOTIFY signal prototype:
NOTIFY signals for MEMBER variables must take zero or one parameter, which must be of the same type as the property.
In QML-side handler of somePropertyChanged signal:
Connections {
target: CppSingleton
onSomePropertyChanged: {
if (someProperty) {
//
}
}
}
symbol someProperty is accessible only if somePropertyChanged signal is declared unary, not nullary. It is handy to have someProperty accessible in signal handler.
When I dispatch PropertiesChanged D-Bus signal, I have a name of a property. It allows to create signal's prototype string "somePropertyChanged()" to get corresponding QObject's method index. Also I have a new value for the property changed. But it is enclosed into QVariant. QMetaMethod::invoke, in turn, accepts Q_ARG values. Therefore I have to use only nullary *Changed signals.
Under the hood Q_ARG is just a QArgument: a pair of typename (const char *) and type-erased value (void *). Evidently it is variant-concept-like thing. I want to convert QVariant to QGenericArgument. Is it possible in general case?
QVariant has similar to QGenericArgument's constructor: QVariant::QVariant(int typeId, const void *copy). But I want to extract both these values. I think it is enough (with aid of some Qt's RTTI, e.g. QVariant's const char *typeToName(int typeId)) to achieve desired.
ADDITIONAL:
There are undocumented methods in QVariant:
void *data();
const void *constData() const;
inline const void *data() const { return constData(); }
Maybe I can use them. But problem of ownership is arose in the case.
ADDITIONAL 2:
Seems QGenericArgument is non-owning being. In conjunction with Qt::DirectConnection in-place-like method call I can use QVariant::data to get acces to QVaraint's internals.
ADDITIONAL 3:
QDBus has a mess with QDBusVariant/QVariant and QDBusArgument internal conversions/representation. It seems unpredictable which "typeinfo" for each property is correctly extracted. (It can be solved using undocumented qdbus_cast< T >(const QVariant &) cast).

Related

How to temporarily switch to the GUI thread

I have a program that takes a long action, I run this function on a different thread. Periodically I need to update the information for the user, so I send a signal to the GUI thread. But sometimes I need the user to make a choice, I need to display the QDialog on the GUI thread and pause the slow thread while the user selects an option, and when the user completes the selection, return the value to the slow thread and continue it
it should look something like this:
But I don’t know how to stop and continue the thread and whether it should be done this way.
Header:
class Example:public QObject
{
//...
Q_OBJECT
void mainLoop();
Example();
signals:
void updateGUI(const QString &message);
void sendQuestion(const QString &message);
void continueMainLoop(const QString &answer);
private slots:
void updatuGUIslot(const QString &message);
void showQuestionDialog(const QString &message);
};
Source:
Example::Example()
{
connect(this,&Example::updateGUI,this,&Example::updatuGUIslot);
connect(this,&Example::sendQuestion,this,&Example::showQuestionDialog);
std::thread t(&Example::mainLoop,this);
t.detach();
// in the project it is not in the constructor
}
void Example::mainLoop()
{
while(some condition1)
{
// slow action
if(some condition2)
emit updateGUI("message");
if(some condition3)
{
QString result;
ThreadPtr th = this_thread(); // pseudocode
connect(this,&Example::continueMainLoop,this,[&](const QString &answer)
{
result = answer;
th.continue(); // pseudocode
});
emit sendQuestion("question");
th.wait(); // pseudocode
}
// slow action
}
}
void Example::showQuestionDialog(const QString &message)
{
// show dialog with question
emit continueMainLoop("answer");
}
void Example::updatuGUIslot(const QString &message)
{
// update GUI
}
you need to invoke the method with BlockingQueuedConnection before condition3 for checking which option that selected by the user.
bool updateGui ;
QMetaObject::invokeMethod(this, "showDialog",Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, updateGui));
if(updateGui)
{
//update GUI
}

How does a function in Qt accept passed arguments from two places?

I want to wirte a funtion CH1_Hard_Soft to process data, which accepts two arguments from two different function.
double MainWindow::getdata_CH1(double time)
{
...
double CH1_data=0;
switch (CH1.Source) {
case 0: //software-hard
CH1_data = CH1_Hard_Soft(time);
....
}
The function CH1_Hard_Soft need to accept an argument time from getdata_CH1 and accept a QVector from other thread. And the function CH1_Hard_Soft will process these data and then return a QVector to getdata_CH1(double time). I don't know how to do this. Please give me some suggestions on how to do this.THANKS!!!
You can use a Function Object: create a new class with two attributes (one per parameter). Create setter for each parameter (or redefine the operator () to be closer to the behavior of a real function).
Each setter should check if the others are setted also. In that case, call you algorithm and send the result with a signal.
For example:
A simple worker executed in another thread. It will send fake data after 3 seconds
class Worker: public QObject
{
Q_OBJECT
public:
Worker(): QObject()
{
}
void timerEvent(QTimerEvent* ev)
{
qDebug() << Q_FUNC_INFO;
emit getVector(QVector<int>() << 2 << 4 << 6 << 8);
killTimer(timerId);
}
public slots:
void run()
{
timerId = startTimer(3000);
}
signals:
void getVector(QVector<int> const& vec);
private:
int timerId;
};
The Function Object: it will accept two param (a double and a vector)
// For convenience. Define a value and a flag to check if the value is well set
template<typename T> struct Param
{
T value;
bool isInit;
Param(): isInit(false)
{}
void setValue(T const& v)
{
value = v;
isInit = true;
}
};
// The processor
class Processor: public QObject
{
Q_OBJECT
public:
Processor(QObject* parent=nullptr): QObject(parent)
{}
void operator()(QVector<int> const& vector)
{
values.setValue(vector);
if (time.isInit)
process();
}
void operator()(double t)
{
time.setValue(t);
if (values.isInit)
process();
}
signals:
void done(double result);
private:
// Will be called as soon as all the parameters are set
void process()
{
// DO something
qDebug() << Q_FUNC_INFO;
emit done(time.value * values.value.length());
}
Param<QVector<int> > values;
Param<double> time;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Run the thread
Worker* worker = new Worker();
QThread* th = new QThread();
worker->moveToThread(th);
QObject::connect(th, &QThread::started, worker, &Worker::run);
Processor CH1_Hard_Soft;
// Will be called when the CH1_Hard_Soft will send its result
QObject::connect(&CH1_Hard_Soft, &Processor::done, [=](double result) { qDebug() << "RESULT" << result; });
// Set the param vector
QObject::connect(worker, &Worker::getVector, [&](QVector<int> const& vec) { CH1_Hard_Soft(vec); });
// Call CH1_Hard_Soft with the first param
double time = 12.6;
CH1_Hard_Soft(time);
th->start();
return app.exec();

How can lambda functions and function typedefinitions work as a listener?

I'm trying to make the equivalent of a Event Listener from Java, but in C++.
My goal is, that I can call a function from a class, which triggers my listener I added to this class.
I found the following Link which gave me a solution to do this.
The problem hereby is, that my program crashed as soon as I tried to call the listeners.
My code is structured like this:
class MessageHandler abstract
{
public:
typedef const std::function<void(int, std::string)> Handler;
void addHandler(Handler& handler) {
handlers.push_back(&handler);
}
private:
std::vector<Handler*> handlers;
protected:
void someFunction(int id, std::string message) {
for (auto& handler : handlers) {
(*handler)(id, message); //Here it will crash
}
}
};
As you maybe already mentioned, this is the base class from which I derive some childclasses. These childclasses call then my "someFunction" code.
And the class where I create one of these childclasses, is structured like this:
class Server
{
private:
SubHandler handler;
void setHandlers() {
handler.addHandler([&](int id, std::string message) { executingFunction(id, message); });
}
void executingFunction(int id, std::string message) {
std::cout << "Listener Worked!" << std::endl;
//Not actually the code inside, but it doesn't matter, case I don't even get to this code
}
};
The program crashes at the line, where I loop over my listeners and call them with error:
"Access violation when reading at position 0x000000000000000010."
(This is translated, so its not the message you will get if you have your Visual Studio set to English)
You should compile your code using /permissive-. The compiler should refuse your code.
void addHandler(Handler& handler) {
handlers.push_back(&handler);
}
You shouldn't be able to send a temporary to this function, but yet you are!
// v----- This lambda is a temporary object --------------------------v
handler.addHandler([&](int id, std::string message) { executingFunction(id, message); });
The lambda object created at that line dies just after the statement is finished.
// v---- pointer to the temporary.
handlers.push_back(&handler);
My recomendation would be to drop the pointer and use std::function object by value. They are made to be used like that:
// abstract is not a C++ keyword.
class MessageHandler /* abstract */
{
public:
// using instead of typedef and non const
using Handler = std::function<void(int, std::string)>;
void addHandler(Handler const& handler) { // const reference
// insert by value
handlers.push_back(handler);
}
private:
// no pointer here.
std::vector<Handler> handlers;
protected:
void someFunction(int id, std::string message) {
for (auto const& handler : handlers) {
handler(id, message); //Here it will not crash anymore
}
}
};
This is because your lambda defined in your Server class method isn't in the scope of your MessageHandler class. I suggest you read through this : https://blog.feabhas.com/2014/03/demystifying-c-lambdas/ to get a good idea of what the problem is and how to fix it.
Though, it might be a good solution to define a struct holding your lambda, which would then work with std::mem_fn.
Hope this helps
Your source is bad :/
You might use instead something like:
class MessageHandler
{
public:
using Handler = std::function<void(int, const std::string&)> Handler;
void addHandler(const Handler& handler) { handlers.push_back(handler); }
void execute(int id, const std::string& message) {
for (auto& handler : handlers) {
(*handler)(id, message);
}
}
private:
std::vector<Handler> handlers;
};
And then use it:
class Server
{
private:
MessageHandler handler;
void setHandlers()
{
handler.addHandler(&Server::executingFunction);
handler.addHandler(
[](int id, const std::string& message)
{
std::cout << message << id << std::endl;
});
}
static void executingFunction(int id, const std::string& message) {
std::cout << "Listener Worked!" << std::endl;
}
};

How can I boost::bind an abstract overriden method, so that the child's method is called?

I'm writing a base class which has an abstract callback. Like this:
class ValueListener
{
public:
ValueListener();
void registerPoint(ValuesSource &mgr, bool create=true);
void valueReceived( QVariant value ) = 0; /* slot */
QString valueName() = 0;
};
The overriding classes shall implement what do they want to do with the received value. But ValueListener itself is responsible for registering the callback:
void ValueListener::registerPoint( ValuesSource& mgr, bool create ) {
ValueSourceInfo* info = mgr.getPoint(valueName(), create);
if(info) {
// Connect the callback
info->valueChanged.connect( boost::bind( &ValueListener::valueReceived, this, _1 ) );
}
}
But obviously, neither this neither the &ValueListener::valueReceived are the things that should be receiving value updates - the overriding class should. So how can I bind the overridden method without knowing it?
Turns out doing this is probably a flawed idea. Instead, I created two methods, one normal and one private:
class ValueListener
{
public:
ValueListener();
void registerPoint(ValuesSource &mgr, bool create=true);
void valueReceived( QVariant value ) = 0;
QString valueName() = 0;
private:
void valueReceivedPrivate( QVariant value ) {valueReceived(value);}; /* slot */
};
And I used connect on the private method:
void ValueListener::registerPoint( ValuesSource& mgr, bool create ) {
ValueSourceInfo* info = mgr.getPoint(valueName(), create);
if(info) {
// Connect the callback
info->valueChanged.connect( boost::bind( &ValueListener::valueReceivedPrivate, this, _1 ) );
}

Avoid dynamic_cast with derived classes (Cast Derived class)

I am new to C++ and came to a point, where I generate an overhead with classes. I have a QTcpSocket and read messages from it and create objects, for example MessageJoin, MessagePart, MessageUserData etc. I send these objects to my client and display them (+ do some UI updating).
Now here comes my problem. I tested a few design techniques but all of them are not that nice:
Pass each parameter of a message object in a signal/slot connection to the client - small overhead but not that good-looking
Create a method for each Message-Type (messageJoinReceived, messageNoticeReceived etc.)
Create one method and use dynamic_cast to cast für each class and test it
For a better understanding, I added my dynamic_cast version. As a said, the code looks ugly and unusable. My questions are:
Is there a better way to do it with (a) dynamic_cast
Is there another way (For example a design pattern) to solve such a problem ? maybe add a method in the classes and return the type or something like this
I read about the visitor pattern. This pattern is just for dynamic object types in Getter/Setter methods ?
A few side notes
I can use RTTI
Speed isn't a big deal. Clean and understandable code is more important
I use Qt and have the possiblity to use qobject_cast and signal/slots
Here is my code (Pastebin-Link):
// Default class - contains the complete message (untouched)
class Message
{
public:
QString virtual getRawMessage() { return dataRawMessage; }
protected:
QString dataRawMessage;
};
// Join class - cointains the name of the joined user and the channel
class MessageJoin : public Message
{
public:
MessageJoin(const QString &rawmessage, const QString &channel, const QString &user)
{
dataRawMessage = rawmessage;
dataChannel = channel;
dataUser = user;
}
QString getChannel() { return dataChannel; }
QString getUser(){ return dataUser; }
private:
QString dataChannel;
QString dataUser;
};
// Notice class - contains a notification message
class MessageNotice : public Message
{
public:
MessageNotice(const QString &rawmessage, const QString &text)
{
dataRawMessage = rawmessage;
dataText = text;
}
QString getText() { return dataText;}
private:
QString dataText;
};
// Client code - print message and update UI
void Client::messageReceived(Message *message)
{
if(message)
{
MessageJoin *messagejoin;
MessagePart *messagepart;
MessageNotice *messagenotice;
if((messagejoin = dynamic_cast<MessageJoin *>(message)) != 0)
{
qDebug() << messagejoin->getUser() << " joined " << messagejoin->getChannel();
// Update UI: Add user
}
else if((messagenotice = dynamic_cast<MessageNotice *>(message)) != 0)
{
qDebug() << messagenotice->getText();
// Update UI: Display message
}
else
{
qDebug() << "Cannot cast message object";
}
delete message; // Message was allocated in the library and is not used anymore
}
}
This looks quite similar to the expression problem and AFAIK there is no way to avoid casts if you are going to add new messages and new ways to handle them. However it's not that hard to make more eye pleasing wrap for necessary run-time stuff. Just create a map from message type to corresponding handler using typeid.
#include <functional>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
typedef std::function<void(Message *)> handler_t;
typedef std::unordered_map<
std::type_index,
handler_t> handlers_map_t;
template <class T, class HandlerType>
handler_t make_handler(HandlerType handler)
{
return [=] (Message *message) { handler(static_cast<T *>(message)); };
}
template <class T, class HandlerType>
void register_handler(
handlers_map_t &handlers_map,
HandlerType handler)
{
handlers_map[typeid(T)] = make_handler<T>(handler);
}
void handle(handlers_map_t const &handlers_map, Base *message)
{
handlers_map_t::const_iterator i = handlers_map.find(typeid(*message));
if (i != handlers_map.end())
{
(i->second)(message);
}
else
{
qDebug() << "Cannot handle message object";
}
}
Then register handlers for specific message types:
handlers_map_t handlers_map;
register_handler<MessageJoin>(
handlers_map,
[] (MessageJoin *message)
{
qDebug() << message->getUser() << " joined " << message->getChannel();
// Update UI: Add user
});
register_handler<MessageNotice>(
handlers_map,
[] (MessageNotice *message)
{
qDebug() << message->getText();
// Update UI: Display message
});
And now you can handle messages:
// simple test
Message* messages[] =
{
new MessageJoin(...),
new MessageNotice(...),
new MessageNotice(...),
new MessagePart(...),
};
for (auto m: messages)
{
handle(handlers_map, m);
delete m;
}
Surely you might want to make some improvements like wrapping handlers stuff into reusable class, using QT or boost signals/slots so you can have multiple handlers for a single message, but the core idea is the same.
The visitor pattern could be a good fit i.e.
class Message
{
public:
QString virtual getRawMessage() { return dataRawMessage; }
virtual void accept(Client& visitor) = 0;
protected:
QString dataRawMessage;
};
// Join class - cointains the name of the joined user and the channel
class MessageJoin : public Message
{
public:
MessageJoin(const QString &rawmessage, const QString &channel, const QString &user)
{
dataRawMessage = rawmessage;
dataChannel = channel;
dataUser = user;
}
QString getChannel() { return dataChannel; }
QString getUser(){ return dataUser; }
void accept(Client& visitor) override
{
visitor.visit(*this);
}
private:
QString dataChannel;
QString dataUser;
};
// Notice class - contains a notification message
class MessageNotice : public Message
{
public:
MessageNotice(const QString &rawmessage, const QString &text)
{
dataRawMessage = rawmessage;
dataText = text;
}
QString getText() { return dataText;}
void accept(Client& visitor) override
{
visitor.visit(*this);
}
private:
QString dataText;
};
void Client::visit(MessageJoin& msg)
{
qDebug() << msg.getUser() << " joined " << msg.getChannel();
// Update UI: Add user
}
void Client::visit(MessageNotice& msg)
{
qDebug() << msg.getText();
// Update UI: Display message
}
// Client code - print message and update UI
void Client::messageReceived(Message *message)
{
if(message)
{
message->visit(this);
delete message; // Message was allocated in the library and is not used anymore
}
}
A better design might be to have an abstract virtual function in the Message class, called process or onReceive or similar, the sub-classes implements this function. Then in Client::messageReceived just call this function:
message->onReceive(...);
No need to for the dynamic_cast.
I would also recommend you to look into smart pointers, such as std::unique_ptr.
If you have private data in the Client class that is needed for the message processing functions, then there are many methods of solving that:
The simplest is to use a plain "getter" function in the client:
class Client
{
public:
const QList<QString>& getList() const { return listContainingUiRelatedStuff; }
// Add non-const version if you need to modify the list
};
If you just want add items to the list in your example, then add a function for that:
void addStringToList(const QString& str)
{ listContainingUiRelatedStuff.push_back(str); }
Or the non-recommended variant, make Client a friend in all message classes.
The second variant is what I recommend. For example, if you have a list of all connected clients and want to send a message to all of them, then create a function sendAll that does it.
The big idea here is to try and minimize the coupling and dependencies between your classes. The less coupling there is, the easier it will be to modify one or the other, or add new message classes, or even completely rewrite one or the other of the involved classes without it affecting the other classes. This is why we split code into interface and implementation and data hiding.