Related
I am working on code to implement a lightweight version of something similar to Qt's signals/slots mechanism (i.e. a glorified Observer pattern) where I can connect "signals" to "slots." A "connection" can only be made if the two signatures are identical. Any time a "signal" is emitted, all of the calls to any attached "slots" will be queued to be executed in their appropriate threads at a later time.
I have been doing a large amount of research on this topic and understand that what I want can be achieved through some combination of Functors and templates. However, I am unable to figure out how to make everything work the way I would like. Also, this is being used on an embedded processor so I don't want to use std::function which, as I have read, has a large amount of overhead associated with it.
So far, I have written a successful signature for a "connect" function as follows:
template<typename OBJECT, typename FUNC>
static void connect(OBJECT *sender, FUNC signal, OBJECT *receiver, FUNC slot) {
}
//...
Test1 t;
Test1::connect(&t, &Test1::signal1, &t, &Test1::slot1);
Now, I need some way to store a function call associated with the object/slot to be stored and called by the signal when it is emitted. I understand that this should be done with a Functor. However, I can't figure out how to write a functor that is agnostic of the object but requires a specific signature. I am looking for something along the lines of:
GenericFunctor<int, int> slotsConnectedToSignal1[4];
GenericFunctor<int, char, int> slotsConnectedToSignal2[4];
In this way, the signal (which has the same signature as the array that contains its connected slots) can loop through the array and call all the functor.
Is there any way to achieve what I am trying to accomplish and am I on the right track?
Thank You!
EDIT
I am getting closer to what I want using the following definitions for connect().
template <typename ObjSender, typename Ret, typename ObjReceiver>
static void connect(ObjSender *sender, Ret(ObjSender::*signal)(), ObjReceiver *receiver, Ret(ObjReceiver::*slot)()) {
std::function<Ret()> fSender = std::bind(signal, sender);
std::function<Ret()> fReceiver = std::bind(slot, receiver);
}
template <typename ObjSender, typename Ret, typename ARG0, typename ObjReceiver>
static void connect(ObjSender *sender, Ret(ObjSender::*signal)(ARG0), ObjReceiver *receiver, Ret(ObjReceiver::*slot)(ARG0)) {
std::function<Ret(ARG0)> fSender = std::bind(signal, sender, std::placeholders::_1);
std::function<Ret(ARG0)> fReceiver = std::bind(slot, receiver, std::placeholders::_1);
}
Now, my next question is how to store and recall these std::function objects in their correct signals. For example, when the user calls signal1(1, 2), this function should be able to look up all the "connected" std::function objects associated with it and call each one in turn with arguments.
Also, I need to mention that this code is targeted for an embedded system which is why I am attempting to develop this from scratch so as to minimize the overhead from external libraries.
EDIT 2
Based upon some of the feedback I have received, the following is my most recent attempt to achieve my desired results.
template<typename ... ARGS>
class Signal {
public:
void operator()(ARGS... args) {
_connection(args...);
}
void connect(std::function<void(ARGS...)> slot) {
_connection = slot;
}
private:
std::function<void(ARGS...)> _connection;
};
class Test2 {
public:
Signal<int, int> signal1;
Signal<int, int> signal2;
void slot1(int a, int b) {
signal1(a, b);
}
void slot2(int c, int d) {
int i = c + d;
(void)i;
}
};
int main(void) {
Test2 t2;
t2.signal1.connect(t2.signal2);
t2.signal2.connect(std::bind(&Test2::slot2, &t2, std::placeholders::_1, std::placeholders::_2));
t2.slot1(1, 2);
}
However, I still have the problem in this case that, when I want to connect to a "slot" function (instead of another signal), I need to used std::bind with the right number of placeholders. I know there must be a way to do this but am not familiar enough with out std::function and lambdas work.
What you are doing in your main() in "EDIT 2" is a little convoluted, but the basic issue is that your first connect() call is making a copy of the signal1 object, which is not the same as a reference to t2's member variable. One way to address this is to capture it with a lambda:
#include <iostream>
#include <functional>
#include <vector>
template<typename ... ARGS>
class Signal {
public:
void operator()(ARGS... args) const {
for( const auto& slot : _connection ) {
slot(args...);
}
}
// Return the index as a handle to unregister later
auto connect(std::function<void(ARGS...)> slot) {
_connection.emplace_back( std::move( slot ) );
return _connection.size() - 1;
}
// ... unregister function, etc.
private:
std::vector<std::function<void(ARGS...)>> _connection;
};
class Test2 {
public:
Signal<int, int> signal1;
Signal<int, int> signal2;
void slot1(int a, int b) {
std::cout << "slot1 " << a << ' ' << b << '\n';
signal1(a,b);
}
void slot2(int c, int d) {
std::cout << "slot2 " << c << ' ' << d << '\n';
}
};
int main() {
Test2 t2;
//t2.signal1.connect( t2.signal2 ); // Makes a copy of t2.signal2
t2.signal1.connect( [&]( auto x, auto y ) { t2.signal2(x,y); } ); // Keeps a reference to t2.signal2
t2.signal2.connect( [&]( auto x, auto y ) { t2.slot2( x, y ); } );
t2.signal1(1, 2);
}
See it live on Wandbox, where it calls only slot2() as expected:
slot2 1 2
FWIW, here is the same code (but shorter!) using Boost.Signals2:
#include <iostream>
#include <boost/signals2.hpp>
class Test2 {
public:
boost::signals2::signal<void(int, int)> signal1;
boost::signals2::signal<void(int, int)> signal2;
void slot1(int a, int b) {
std::cout << "slot1 " << a << ' ' << b << '\n';
signal1(a,b);
}
void slot2(int c, int d) {
std::cout << "slot2 " << c << ' ' << d << '\n';
}
};
int main() {
Test2 t2;
//t2.signal1.connect( t2.signal2 );
t2.signal1.connect( [&]( auto x, auto y ) { t2.signal2(x,y); } );
t2.signal2.connect( [&]( auto x, auto y ) { t2.slot2( x, y ); } );
t2.signal1(1, 2);
}
The only differences are the #includes, the declarations of signal1 and signal2, and the deletion of the home-spun Signal class. See it live on Wandbox.
Note that when Qt does this, it relies on macros and the MOC to generate some extra glue code to make it all work.
Update: Replying to your comment, yes. To support that syntax, you could add an overload that does the binding work for the user. You may get into some complications with having to supply multiple overloads for different const-nesses, but this should give you an idea:
#include <iostream>
#include <functional>
#include <utility>
#include <vector>
template<typename ... ARGS>
class Signal {
public:
void operator()(ARGS... args) const {
for( const auto& slot : _connection ) {
slot(args...);
}
}
template<class T>
auto connect(T& t, void(T::* fn)(ARGS...)) {
const auto lambda = [&t, fn](ARGS... args) {
(t.*fn)( std::forward<ARGS>( args )... );
};
return connect( lambda );
}
auto connect(std::function<void(ARGS...)> slot) {
_connection.emplace_back( std::move( slot ) );
return _connection.size() - 1;
}
private:
std::vector<std::function<void(ARGS...)>> _connection;
};
class Sender {
public:
Signal<int, int> signal1;
};
class Receiver {
public:
void slot1(int a, int b) {
std::cout << "slot1 " << a << ' ' << b << '\n';
}
void slot2(int a, int b) {
std::cout << "slot2 " << a << ' ' << b << '\n';
}
};
// Stand-alone slot
void slot3(int a, int b) {
std::cout << "slot3 " << a << ' ' << b << '\n';
}
int main() {
auto sender = Sender{};
auto recv = Receiver{};
// Register three different slots
sender.signal1.connect( [&]( auto x, auto y ) { recv.slot1( x, y ); } );
sender.signal1.connect( recv, &Receiver::slot2 );
sender.signal1.connect( &slot3 );
// Fire the signal
sender.signal1(1, 2);
}
I refactored a little and hopefully made it a little easier to grok. See it live on Wandbox, where it outputs the expected:
slot1 1 2
slot2 1 2
slot3 1 2
I think I have finally come up with a method that works for me based upon all the comments I have received. Here is a simplified version for anyone who is interested:
template<typename ... ARGS>
class Signal {
#define MAX_CONNECTIONS 4
public:
#define CONNECT_FAILED (ConnectionHandle)(-1);
Signal() : _connections{nullptr} {};
/**
* #brief Implementation of the "function" operator
*
* #param args Arguments passed to all connected slots (or signals)
*/
void operator()(ARGS... args) {
//Loop through the connections array
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (_connections[i]) {
/*
* Call the connected function
* This will either
* a) Call a lambda which will invoke a slot
* b) Call operator() on another signal (i.e. recursive signals)
*/
_connections[i](args...);
}
}
}
/**
* #brief Make a connection to a slot
*
* #param t A pointer to the object instance
* #param fn A member function pointer to the slot
* #return A handle (ID) used to disconnect the connection if desired
*
* #note This function assumes that T is a subclass of ActiveObject (i.e. has the invoke() method)
*/
template<class T>
inline ConnectionHandle connect(T* t, void(T::* fn)(ARGS...)) {
//This lambda will use ActiveObject::invoke to queue the connected slot for later execution
const auto lambda = [=](ARGS... args) { T::invoke(t, fn, args...); };
return connect(lambda);
}
/**
* #brief Make a connection to another signal
*
* #param t A pointer to the object instance
* #param s The signal
* #return A handle (ID) used to disconnect the connection if desired
*/
template<class T>
inline ConnectionHandle connect(T *t, Signal<ARGS...> T::*s) {
return connect(t->*s);
}
/**
* #brief Make a generic connection to a slot which takes an Event smart pointer as its argument
*
* #param t The object to connect to
* #param fn The member function of t to connect to
* #param eventIndex The user-defined index to assign to the event
* #return A handle (ID) used to disconnect the connection if desired
*
* #note This version of connect is useful to connect any signal to the same slot function which may dispatch the eent directly into its state machine (if derived from StateMachine)
*/
template<class T>
inline ConnectionHandle connect(T *t, void(T::* fn)(std::shared_ptr<Event> e), int eventIndex) {
const auto lambda = [=](ARGS... args){
std::shared_ptr<Event> ptr = std::make_shared<Event>(eventIndex);
T::invoke(t, fn, ptr);
};
return connect(lambda);
}
/**
* #brief Make a connection to an abstract function
*
* #param slot The function to connect to
* #return A handle (ID) used to disconnect the connection if desired
*/
ConnectionHandle connect(std::function<void(ARGS...)> slot) {
ConnectionHandle i;
//2nd: Make the connection in an empty slot
for (i = 0; i < MAX_CONNECTIONS; i++) {
if (!_connections[i]) {
//Make the connection
_connections[i] = slot;
return i;
}
}
return CONNECT_FAILED;
}
/**
* #brief Remove the given connection by its handle (i.e. ID)
* #param h The handle previously returned by a call to connect()
*/
void disconnect(ConnectionHandle h) {
if ((h < 0) || (h >= MAX_CONNECTIONS)) return;
_connections[h] = nullptr;
}
private:
std::function<void(ARGS...)> _connections[MAX_CONNECTIONS];
};
Where any object that wants to take advantage of signals and slots need to subclass the following:
class ActiveObject {
public:
#define DECLARE_SIGNAL(name,...) Signal<__VA_ARGS__> name
#define EMIT
#define DECLARE_SLOT(name, ...) void name(__VA_ARGS__)
#define DEFINE_SLOT(className, name, ...) void className::name(__VA_ARGS__)
ActiveObject(ActiveObjectThread *parentThread);
virtual ~ActiveObject();
/**
* #brief Called by the parent thread during initialization once the thread has started and is running
* #note This function may be overridden by sub-classes to provide initialization code
*/
virtual void initialize() {};
/**
* #brief Return the parent thread of this active object
*/
inline ActiveObjectThread *thread() const { return _parentThread; }
/**
* #brief Queue a slot to be called later by the parent thread
*
* #param t A pointer to the active object
* #param fn A member function pointer within the active object to execute
* #param args The arguments to pass to the slot function when called
* #note Invoke should ALWAYS be used when calling a slot function to ensure that it is executed within the same thread as the active object (i.e. the parent thread)
*/
template<class T, typename ... ARGS>
inline static void invoke(T *t, void(T::* fn)(ARGS...), ARGS... args) {
std::function<void()> *f = new std::function<void()>([=]() { (t->*fn)( args... ); });
//Queue in the parent thread
t->_parentThread->queueInvokable(f);
}
inline static void invoke(ActiveObject *ao, std::function<void()> f) {
std::function<void()> *newF = new std::function<void()>(f);
ao->_parentThread->queueInvokable(newF);
}
private:
ActiveObjectThread *_parentThread;
};
In my application, I am not calling any of the slots directly, but rather am queuing them for later execution by a thread.
Here is an example of how to use these classes:
class MyActiveObject : public ActiveObjecte {
public:
MyActiveObject() :
//Just create a thread as part of the object's constructor
ActiveObject(new ActiveObjectThread("MyActiveObject", 512, 1))
{
thread()->Start();
}
~MyActiveObject() {
//Make sure to delete the thread we created
delete thread();
}
DECLARE_SIGNAL(signal1, int, int);
DECLARE_SLOT(slot1) {
GenericEvent *e = new GenericEvent(EVENT_1);
e->args()[0] = 100;
//Dispatch the event into the state machine
dispatch(e);
EMIT signal1(5, 6);
}
DECLARE_SLOT(slot2, int a, int b) {
GenericEvent *e = new GenericEvent(EVENT_2);
e->args()[0] = a;
e->args()[1] = b;
//Dispatch the event into the state machine
dispatch(e);
}
DECLARE_SLOT(slotEventHander, std::shared_ptr<Event> e) {
dispatch(e.get());
}
};
MyActiveObject myActiveObject;
myActiveObject.signal1.connect(&myActiveObject, &MyActiveObject::slot2);
myActiveObject.signal1.connect(&myActiveObject, &MyActiveObject::slotEventHander, MyActiveObject::EVENT_2);
ActiveObject::invoke(&myActiveObject, &MyActiveObject::slot1);
I have taken out the code that implements the state machine because that is not relevant to this topic.
I hope this helps someone out!
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).
Good day everyone, I discovered a bug in my code, that is:
I have a list of pointers to QLocalSocket and in the destructor I close and delete them with the following piece of code
for ( int i = 0; i < localSocketsList.size(); i++ )
{
if ( localSocketsList.at(i) != NULL )
{
localSocketsList.at(i)->close();
localSocketsList.at(i)->deleteLater();
}
}
The bug was that I previously connected a slot with the disconnected() signal of the sockets, and the slot delete them as well with the code:
QMutableListIterator<QLocalSocket *> iterator(localSocketsList);
while( iterator.hasNext() )
{
QLocalSocket * currentLocalSocket = iterator.next();
if ( currentLocalSocket -> state() == QLocalSocket::UnconnectedState )
{
currentLocalSocket -> deleteLater();
iterator.remove();
}
}
Ok right now you can see the error, I try to delete a pointer twice and I have a crash. BUT, it took me a while to detect this error because I did not observe a crash in Windows 10, only in Windows 7.
The question is: is there some difference in the deleteLater() function of Qt5.7 depending of the operative system? Shouldn't this problem appear in all platforms, since it is a c++ run time error?
Maybe is something depending of how Qt schedules the job (I mean, finishing the for cycle before sending the signals)? In that case is the schedule of the jobs depending of the OS? Shouldn't this be almost "random"?
Thanks everybody
It is valid to invoke deleteLater multiple times before the control returns to the event loop:
#include <QtCore>
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
auto obj = new QObject;
obj->deleteLater();
obj->deleteLater();
connect(obj, &QObject::destroyed, []{ qApp->quit(); });
return app.exec();
}
Thus, it seems to me that your problem isn't in invoking deleteLater multiple times, but iterating over a collection of objects that have been destroyed. The localSocketList is not aware of the sockets being deleted and contains dangling pointers.
There's a simple remedy for that - use a list that is aware of objects being deleted. For simplicity, the list below is explicitly shared, i.e. any of its copies refer to the same object (those are also JavaScript semantics, if you're familiar with them).
// https://github.com/KubaO/stackoverflown/tree/master/questions/qobject-pointer-list-43986348
#include <QtCore>
class PointerListData : public QObject, public QSharedData {
Q_OBJECT
public:
QVector<QObject*> list;
void removed() { list.removeAll(sender()); }
void connect(QObject* obj) {
QObject::connect(obj, &QObject::destroyed, this, &PointerListData::removed);
}
void disconnect(QObject* obj) {
QObject::disconnect(obj, &QObject::destroyed, this, &PointerListData::removed);
}
};
template <typename T> class PointerList {
protected:
QExplicitlySharedDataPointer<PointerListData> d;
public:
PointerList() : d(new PointerListData) {}
PointerList(const PointerList &other) : d(other.d) {}
PointerList(PointerList && other) : d(std::move(other.d)) {}
void append(T* obj) {
auto connect = !contains(obj);
d->list.append(obj);
if (connect)
d->connect(obj);
}
PointerList & operator<<(T* obj) {
append(obj);
return *this;
}
int removeAll(T* obj) {
auto n = d->list.removeAll(obj);
if (n)
d->disconnect(obj);
return n;
}
bool contains(T* obj) const {
return d->list.contains(obj);
}
void clear() {
for (auto obj : d->list)
d->disconnect(obj);
d->list.clear();
}
void moveToThread(QThread* thread) { d->moveToThread(thread); }
bool isEmpty() const { return d->list.isEmpty(); }
int size() const { return d->list.size(); }
using iterator = T**;
using const_iterator = const T**;
iterator begin() { return iterator(d->list.data()); }
iterator end() { return iterator(d->list.data() + d->list.size()); }
const_iterator begin() const { return const_iterator(d->list.constData()); }
const_iterator end() const { return const_iterator(d->list.constData() + d->list.size()); }
constexpr const PointerList& crange() const noexcept { return *this; }
// see http://stackoverflow.com/q/15518894/1329652
};
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
PointerList<QMimeData> list;
{
QMimeData a;
QMimeData b;
list << &a << &b;
auto list2 = list;
Q_ASSERT(list2.size() == 2);
for (auto obj : list.crange())
qDebug() << obj;
}
Q_ASSERT(list.isEmpty());
}
#include "main.moc"
I've a thread that read datas
class MyThread: QThread
{
...
}
void MyThread::run ()
{
uint8_t* buffer; // in my real code, it's a ring, so there is not read during write
// ...
while (true)
{
if (isInterruptionRequested())
return;
USB_READ(buffer);
emit newData(buffer);
}
}
In my UI Class I have:
connect(this, &UIClass::newData, m_thread, &MyThread::newData);
// ...
void newData(uint8_t* data)
{
// Process data
}
void UIClass::closeEvent(QCloseEvent *event)
{
disconnect(this, &UIClass::newData, m_thread, &MyThread::newData);
m_thread->requestInterruption();
m_thread->wait();
}
The problem with that if, when I click on "close", the thread is destroyed that cause the pointer data to be invalid. The signal newData is sometimes called that cause my function to work with invalid pointer and segfault. How to be sure that is not gonna happend ?
For now, I use a std::this_thread::sleep_for() with an arbitrary delay, it works, but I not find this very beautiful
That I have in my mind :
- disconnect the signal
- wait for the pendings signals to be executed
- exit
The problem is that you send a pointer from one thread to another without ensuring the pointer stays valid.
You have multiple choices to solve this. Either use QSharedPointer (or similar utilities from the stl) to hold your data, doing so will ensure your pointer will remain valid (or provide you a way to detect when the pointer becomes invalid if you also use QWeakPointer). Or you could make use of QByteArray to pass the data, but this will make a copy.
Example 1
void MyThread::run ()
{
QSharedPointer<uint8_t> buffer (new uint8_t[N]()); // Do not delete[], QSharedPointer will handle it
...
emit newData(buffer);
}
void newData(QSharedPointer<uint8_t> data)
{
// data is always valid
// Process data
}
Example 2
void MyThread::run ()
{
QSharedPointer<uint8_t> buffer (new uint8_t[N]());
...
emit newData(buffer);
}
void newData(QWeakPointer<uint8_t> data)
{
// data might not be valid but we can check
QSharedPointer<uint8_t> buffer (data);
if (!buffer)
return;
// Process data
}
Example 3
void MyThread::run ()
{
uint8_t[N] buffer;
...
emit newData(QByteArray(buffer, size));
}
void newData(QByteArray data)
{
// data is valid
// Process data
}
All you need to do is for the thread to outlive the user interface. That's rather easy:
class MyThread : public QThread
{
Q_OBJECT
RingBuffer buffer;
public:
void run() override;
~MyThread() {
requestInterruption();
quit();
wait();
}
Q_SIGNAL newData(RingBuffer *);
};
int main(int argc, char **argv) {
QApplication app{argc, argv};
MyThread thread;
thread.start();
UIClass ui;
connect(&thread, &MyThread::newData, &ui, &UIClass::newData);
return app.exec();
}
Below I have attempted to write a sudo code for the Observer pattern when observers wish to observe different items.
Ignore the syntax errors. I wish to know if this is the correct way to implement this. If not, please suggest better ways.
// Used by the subject for keeping a track of what items the observer wants to observe
typedef struct observerListStruct
{
bool getTemperatureUpdate;
bool getHumidityUpdate;
bool getPressureUpdate;
observer's-function pointer's address;
};
// Subject's class
class weatherData
{
public:
// Observers will call this function to register themselves. The function pointer will point to the function which will get called when updates are available.
void registerObservers (observer obj, observer's-FunctionPointer)
{
// This observer's function returns which items to observe.
char* f = obj.returnItemsToObserve ();
if f[0] = `1`
observerListStruct.getTemperatureUpdate = true;
}
void unregisterObservers (observer obj) {}
private:
vector <observerListStruct> observerList;
float temperature;
float humidity;
float pressure;
void notifyObservers () {}
float getTemperature () {}
float getHumidity () {}
float getPressure () {}
} weatherDataObject;
// Base class for observers containing common functions
class observers
{
char ItemsToObserve [3] = {1, 2, 3};
// This observer's function returns which items to observe. Default - return all items
virtual char* returnItemsToObserve ()
{
return ItemsToObserve;
}
};
class observerDisplayElementCurrentConditions : public observers
{
char ItemsToObserve [3] = {1, 2};
char* returnItemsToObserve ()
{
return ItemsToObserve;
}
// this function will be used as a function pointer for getting updates
void getUpdatesAndDisplayWeatherData (float, float) {}
};
A more pattern oriented solution (but without function pointers) could be the following. You could parametrize the WeatherObserver-Class to get only the values, you want.
#include <list>
#include <iostream>
class Observable; //forward declaration
//Base class for all observers
class Observer {
friend class Observable; //allow access to observedSubject
protected:
Observable *observedSubject;
public:
virtual void update(){};
};
//Base class for all observables
class Observable {
private:
std::list<Observer * const> m_registeredObservers;
public:
~Observable()
{
//delete the observers
std::list<Observer * const>::iterator it = m_registeredObservers.begin();
while (it != m_registeredObservers.end())
{
delete *it;
it = m_registeredObservers.erase(it);
}
}
void addObserver(Observer * const _pObserver)
{
_pObserver->observedSubject = this;
m_registeredObservers.push_back(_pObserver);
}
void removeObserver(Observer * const _pObserver)
{
m_registeredObservers.remove(_pObserver);
delete _pObserver;
}
void notifyObservers()
{
std::list<Observer * const>::iterator it = m_registeredObservers.begin();
while (it != m_registeredObservers.end())
{
(*it)->update();
it++;
}
}
};
//Concrete Observable
class WeatherData : public Observable {
private:
float temperature;
float humidity;
float pressure;
public:
WeatherData(): temperature(0), humidity(0), pressure(0)
{};
float getTemperature () const
{
return temperature;
}
float getHumidity () const
{
return humidity;
}
float getPressure () const
{
return pressure;
}
void setTemperature(float _temperature)
{
if (temperature != _temperature)
{
temperature = _temperature;
notifyObservers();
}
}
void setHumidity(float _humidity)
{
if (humidity != _humidity)
{
humidity = _humidity;
notifyObservers();
}
}
void setPressure(float _pressure)
{
if (pressure != _pressure)
{
pressure = _pressure;
notifyObservers();
}
}
};
//Concrete implementation of an weather observer
class WeatherObserver : public Observer
{
public:
WeatherObserver():Observer(){};
void update()
{
WeatherData* pWeatherPtr = static_cast<WeatherData*>(observedSubject);
if (pWeatherPtr != 0)
{
float actHumidity = pWeatherPtr->getHumidity();
float actPressure = pWeatherPtr->getPressure();
float actTemperature = pWeatherPtr->getTemperature();
//do something with the data
std::cout << "WeatherObserver update" << std::endl;
std::cout << "Temperature : " << actTemperature << std::endl;
std::cout << "Humidity : " << actHumidity << std::endl;
std::cout << "Pressure : " << actPressure << std::endl;
}
}
};
int main()
{
WeatherData weatherData;
Observer * pObserver = new WeatherObserver();
weatherData.addObserver(pObserver);
weatherData.setHumidity(100);
weatherData.setTemperature(100);
}
#include <algorithm>
#include <vector>
class WeatherFlags
{
public:
WeatherFlags()
: mask_(0)
{}
union {
struct {
unsigned int temperature_ : 1;
unsigned int humidity_ : 1;
unsigned int pressure_ : 1;
};
unsigned int mask_;
};
};
class WeatherData;
class WeatherEvent
{
public:
WeatherEvent(WeatherData* data, WeatherFlags const& flags)
: data_(data)
, flags_(flags)
{}
double getTemperature() const;
WeatherData* data_;
WeatherFlags flags_;
};
class WeatherListener
{
public:
virtual ~WeatherListener() = 0;
virtual void onWeatherUpdate(WeatherEvent& e) = 0;
};
inline WeatherListener::~WeatherListener() {}
class WeatherListenerEntry
{
public:
WeatherListenerEntry()
: listener_(0)
{}
WeatherListenerEntry(WeatherListener* listener, WeatherFlags const& flags)
: listener_(listener)
, flags_(flags)
{}
WeatherListener* listener_;
WeatherFlags flags_;
};
class WeatherData
{
public:
WeatherData();
void addListener(WeatherListener* listener, WeatherFlags const& flags);
void removeListener(WeatherListener* listener);
void notify(WeatherFlags const& flags);
double getTemperature() const { return temperature_; }
private:
typedef std::vector<WeatherListenerEntry> Listeners;
Listeners listeners_;
double temperature_;
};
WeatherData::WeatherData()
: temperature_(0)
{}
void WeatherData::addListener(WeatherListener* listener, WeatherFlags const& flags)
{
// TODO Could maybe check for the addition of duplicates here...
listeners_.push_back(WeatherListenerEntry(listener, flags));
}
void WeatherData::removeListener(WeatherListener* listener)
{
struct ListenerEquals {
WeatherListener* listener_;
ListenerEquals(WeatherListener* listener)
: listener_(listener)
{}
bool operator()(WeatherListenerEntry const& e) const {
return (e.listener_ == listener_);
}
};
listeners_.erase(
std::remove_if(listeners_.begin(), listeners_.end(), ListenerEquals(listener)),
listeners_.end());
}
void WeatherData::notify(WeatherFlags const& flags)
{
WeatherEvent evt(this, flags);
for (Listeners::iterator i = listeners_.begin(); i != listeners_.end(); ++i)
{
if (0 != (i->flags_.mask_ & flags.mask_)) {
i->listener_->onWeatherUpdate(evt);
}
}
}
double
WeatherEvent::getTemperature() const
{
return data_->getTemperature();
}
#include <iostream>
class WeatherObserverStdout : public WeatherListener
{
public:
void observe(WeatherData& data) {
WeatherFlags flags;
flags.temperature_ = true; // interested in temperature only.
data.addListener(this, flags);
}
virtual void onWeatherUpdate(WeatherEvent& e);
};
void
WeatherObserverStdout::onWeatherUpdate(WeatherEvent& e)
{
double temp = e.getTemperature();
std::cout << "Temperatrure: " << temp << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
WeatherData wdata;
WeatherObserverStdout obs;
obs.observe(wdata);
WeatherFlags flags;
wdata.notify(flags);
flags.temperature_ = true;
wdata.notify(flags);
return 0;
}
I think it is easier, and more scalable, to define a set of event types that each observer can listen to. Then you register the observer to listen to that particular event type. The observed then keeps a list of observers registered for each event, and notifies them if and when the event occurs. Using a combination of std::function, std::bind (or boost equivalents), it is easy to register callbacks for a given event type. You could put the callbacks in a map of event type to callback.
For example, something along these lines (almost pseudo-code, has not been tested)
class Publisher {
public :
void subscribe(const std::string& event,
std::function<void(double)> callback) {
m_subscribers[s].push_back(callback);
}
void publish(const std::string& event) const {
for (auto& f : m_subscribers[event]) f( some double );}
void event(const std::string& event) const { publish(event);}
private:
// map of event types (here simply strings) to list of callbacks
std::map<std::string&,
std::list<std::function<void(const std::string&)>>> m_subscribers;
};
struct Foo {
void foo(double x) {
std::cout << "Foo received message: " << x << "\n";
}
};
struct Bar {
void bar(double x) {
std::cout << "Bar received message: " << x << "\n";
}
};
int main() {
Publisher pub;
Foo f0;
Foo f1;
Bar bar0;
pub.subscribe("RED", std::bind(&Foo::foo, &foo0, _1));
pub.subscribe("GREEN", std::bind(&Foo::foo, &foo1, _1));
pub.subscribe("WHITE", std::bind(&Foo::foo, &foo1, _1));
pub.subscribe("RED", std::bind(&Bar::bar, &bar0, _1));
pub.subscribe("BLUE", std::bind(&Bar::bar, &bar0, _1));
pub.subscribe("MAGENTA", std::bind(&Bar::bar, &bar0, _1));
// trigger a "GREEN" event
pub.event("GREEN");
}
Here, the observers (or subscribers) register to some events, represented by strings here, and their registered callbacks get called when this event happens. In the example above I manually trigger an event to illustrate the mechanism.
This event-callback mechanism allows to decouple the actual items from the callback action. The Observed (or publisher) knows what parameter to pass the callback for a given event, and which callbacks to call, so the observers are not dependent on the internal data of the observed object.
I write a lot of C++ code and needed to create an Observer for some game components I was working on. I needed something to distribute "start of frame", "user input", etc., as events in the game to interested parties.
I also wanted more granularity in the events that could be handled. I have a lot of little things that go off...I don't need to have the parts that are interested in resetting for the next frame worried about a change in the user input.
I also wanted it to be straight C++, not dependent on the platform or a specific technology (such as boost, Qt, etc.) because I often build and re-use components (and the ideas behind them) across different projects.
Here is the rough sketch of what I came up with as a solution:
The Observer is a singleton with keys (enumerated values, not strings; this is a speed tradeoff since the keys are not searched hashed, but it means no easy "string" names and you have to define them ahead of time) for Subjects to register interest in. Because it is a singleton, it always exists.
Each subject is derived from a common base class. The base class has an abstract virtual function Notify(...) which must be implemented in derived classes, and a destructor that removes it from the Observer (which it can always reach) when it is deleted.
Inside the Observer itself, if Detach(...) is called while a Notify(...) is in progress, any detached Subjects end up on a list.
When Notify(...) is called on the Observer, it creates a temporary copy of the Subject list. As it iterates over it, it compare it to the recently detached. If the target is not on it, Notify(...) is called on the target. Otherwise, it is skipped.
Notify(...) in the Observer also keeps track of the depth to handle cascading calls (A notifies B, C, D, and the D.Notify(...) triggers a Notify(...) call to E, etc.)
This is what the interface ended up looking like:
/*
The Notifier is a singleton implementation of the Subject/Observer design
pattern. Any class/instance which wishes to participate as an observer
of an event can derive from the Notified base class and register itself
with the Notiifer for enumerated events.
Notifier derived classes MUST implement the notify function, which has
a prototype of:
void Notify(const NOTIFIED_EVENT_TYPE_T& event)
This is a data object passed from the Notifier class. The structure
passed has a void* in it. There is no illusion of type safety here
and it is the responsibility of the user to ensure it is cast properly.
In most cases, it will be "NULL".
Classes derived from Notified do not need to deregister (though it may
be a good idea to do so) as the base class destructor will attempt to
remove itself from the Notifier system automatically.
The event type is an enumeration and not a string as it is in many
"generic" notification systems. In practical use, this is for a closed
application where the messages will be known at compile time. This allows
us to increase the speed of the delivery by NOT having a
dictionary keyed lookup mechanism. Some loss of generality is implied
by this.
This class/system is NOT thread safe, but could be made so with some
mutex wrappers. It is safe to call Attach/Detach as a consequence
of calling Notify(...).
*/
class Notified;
class Notifier : public SingletonDynamic<Notifier>
{
public:
typedef enum
{
NE_MIN = 0,
NE_DEBUG_BUTTON_PRESSED = NE_MIN,
NE_DEBUG_LINE_DRAW_ADD_LINE_PIXELS,
NE_DEBUG_TOGGLE_VISIBILITY,
NE_DEBUG_MESSAGE,
NE_RESET_DRAW_CYCLE,
NE_VIEWPORT_CHANGED,
NE_MAX,
} NOTIFIED_EVENT_TYPE_T;
private:
typedef vector<NOTIFIED_EVENT_TYPE_T> NOTIFIED_EVENT_TYPE_VECTOR_T;
typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T> NOTIFIED_MAP_T;
typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T>::iterator NOTIFIED_MAP_ITER_T;
typedef vector<Notified*> NOTIFIED_VECTOR_T;
typedef vector<NOTIFIED_VECTOR_T> NOTIFIED_VECTOR_VECTOR_T;
NOTIFIED_MAP_T _notifiedMap;
NOTIFIED_VECTOR_VECTOR_T _notifiedVector;
NOTIFIED_MAP_ITER_T _mapIter;
// This vector keeps a temporary list of observers that have completely
// detached since the current "Notify(...)" operation began. This is
// to handle the problem where a Notified instance has called Detach(...)
// because of a Notify(...) call. The removed instance could be a dead
// pointer, so don't try to talk to it.
vector<Notified*> _detached;
int32 _notifyDepth;
void RemoveEvent(NOTIFIED_EVENT_TYPE_VECTOR_T& orgEventTypes, NOTIFIED_EVENT_TYPE_T eventType);
void RemoveNotified(NOTIFIED_VECTOR_T& orgNotified, Notified* observer);
public:
virtual void Reset();
virtual bool Init() { Reset(); return true; }
virtual void Shutdown() { Reset(); }
void Attach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
// Detach for a specific event
void Detach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
// Detach for ALL events
void Detach(Notified* observer);
/* The design of this interface is very specific. I could
* create a class to hold all the event data and then the
* method would just have take that object. But then I would
* have to search for every place in the code that created an
* object to be used and make sure it updated the passed in
* object when a member is added to it. This way, a break
* occurs at compile time that must be addressed.
*/
void Notify(NOTIFIED_EVENT_TYPE_T, const void* eventData = NULL);
/* Used for CPPUnit. Could create a Mock...maybe...but this seems
* like it will get the job done with minimal fuss. For now.
*/
// Return all events that this object is registered for.
vector<NOTIFIED_EVENT_TYPE_T> GetEvents(Notified* observer);
// Return all objects registered for this event.
vector<Notified*> GetNotified(NOTIFIED_EVENT_TYPE_T event);
};
/* This is the base class for anything that can receive notifications.
*/
class Notified
{
public:
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const void* eventData) = 0;
virtual ~Notified();
};
typedef Notifier::NOTIFIED_EVENT_TYPE_T NOTIFIED_EVENT_TYPE_T;
NOTE: The Notified class has a single function, Notify(...) here. Because the void* is not type safe, I created other versions where notify looks like:
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, int value);
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const string& str);
Corresponding Notify(...) methods were added to the Notifier itself. All these used a single function to get the "target list" then called the appropriate function on the targets. This works well and keeps the receiver from having to do ugly casts.
This seems to work well. The solution is posted on the web here along with the source code. This is a relatively new design, so any feedback is greatly appreciated.
My two cents...
Classic (Gang of Four) implementation of Observer pattern notifies observer on changes in any property of the subject. In your question you want to register observer to particular properties, not to a subject as a whole. You can move Observer pattern one level down and take properties as concrete subjects and define their observers (per property) but there is one nicer way to solve this problem.
In C# Observer pattern is implemented through events and delegates. Delegates represent event handlers - functions that should be executed when an event is fired. Delegates can be added (registered) or removed(unregistered) from events.
In C++, functors act as delegates - they can store all necessary information to call some global function or class method in a different context. Events are collections of (registered) functors and when event is raised (called) it basically goes through that list and calls all functors (see Publisher::publish method in juanchopanza's solution).
I tried to implement C++ version of events and delegates and use them in modified Observer pattern which could be applied in your case. This is what I came up with:
#include <list>
#include <iostream>
#include <algorithm>
// use base class to resolve the problem of how to put into collection objects of different types
template <typename TPropertyType>
struct PropertyChangedDelegateBase
{
virtual ~PropertyChangedDelegateBase(){};
virtual void operator()(const TPropertyType& t) = 0;
};
template <typename THandlerOwner, typename TPropertyType>
struct PropertyChangedDelegate : public PropertyChangedDelegateBase<TPropertyType>
{
THandlerOwner* pHandlerOwner_;
typedef void (THandlerOwner::*TPropertyChangeHandler)(const TPropertyType&);
TPropertyChangeHandler handler_;
public:
PropertyChangedDelegate(THandlerOwner* pHandlerOwner, TPropertyChangeHandler handler) :
pHandlerOwner_(pHandlerOwner), handler_(handler){}
void operator()(const TPropertyType& t)
{
(pHandlerOwner_->*handler_)(t);
}
};
template<typename TPropertyType>
class PropertyChangedEvent
{
public:
virtual ~PropertyChangedEvent(){};
void add(PropertyChangedDelegateBase<TPropertyType>* const d)
{
std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d);
if(it != observers_.end())
throw std::runtime_error("Observer already registered");
observers_.push_back(d);
}
void remove(PropertyChangedDelegateBase<TPropertyType>* const d)
{
std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = std::find(observers_.begin(), observers_.end(), d);
if(it != observers_.end())
observers_.remove(d);
}
// notify
void operator()(const TPropertyType& newValue)
{
std::list<PropertyChangedDelegateBase<TPropertyType>* const>::const_iterator it = observers_.begin();
for(; it != observers_.end(); ++it)
{
(*it)->operator()(newValue);
}
}
protected:
std::list<PropertyChangedDelegateBase<TPropertyType>* const> observers_;
};
// class that owns concrete subjects
class PropertyOwner1
{
int property1_;
float property2_;
public:
PropertyChangedEvent<int> property1ChangedEvent;
PropertyChangedEvent<float> property2ChangedEvent;
PropertyOwner1() :
property1_(0),
property2_(0.0f)
{}
int property1() const {return property1_;}
void property1(int n)
{
if(property1_ != n)
{
property1_ = n;
std::cout << "PropertyOwner1::property1(): property1_ set to " << property1_ << std::endl;
property1ChangedEvent(property1_);
}
}
float property2() const {return property2_;}
void property2(float n)
{
if(property2_ != n)
{
property2_ = n;
std::cout << "PropertyOwner1::property2(): property2_ set to " << property2_ << std::endl;
property2ChangedEvent(property2_);
}
}
};
// class that owns concrete subjects
class PropertyOwner2
{
bool property1_;
double property2_;
public:
PropertyChangedEvent<bool> property1ChangedEvent;
PropertyChangedEvent<double> property2ChangedEvent;
PropertyOwner2() :
property1_(false),
property2_(0.0)
{}
bool property1() const {return property1_;}
void property1(bool n)
{
if(property1_ != n)
{
property1_ = n;
std::cout << "PropertyOwner2::property1(): property1_ set to " << property1_ << std::endl;
property1ChangedEvent(property1_);
}
}
double property2() const {return property2_;}
void property2(double n)
{
if(property2_ != n)
{
property2_ = n;
std::cout << "PropertyOwner2::property2(): property2_ set to " << property2_ << std::endl;
property2ChangedEvent(property2_);
}
}
};
// class that observes changes in property1 of PropertyOwner1 and property1 of PropertyOwner2
struct PropertyObserver1
{
void OnPropertyOwner1Property1Changed(const int& newValue)
{
std::cout << "\tPropertyObserver1::OnPropertyOwner1Property1Changed(): \n\tnew value is: " << newValue << std::endl;
}
void OnPropertyOwner2Property1Changed(const bool& newValue)
{
std::cout << "\tPropertyObserver1::OnPropertyOwner2Property1Changed(): \n\tnew value is: " << newValue << std::endl;
}
};
// class that observes changes in property2 of PropertyOwner1 and property2 of PropertyOwner2
struct PropertyObserver2
{
void OnPropertyOwner1Property2Changed(const float& newValue)
{
std::cout << "\tPropertyObserver2::OnPropertyOwner1Property2Changed(): \n\tnew value is: " << newValue << std::endl;
}
void OnPropertyOwner2Property2Changed(const double& newValue)
{
std::cout << "\tPropertyObserver2::OnPropertyOwner2Property2Changed(): \n\tnew value is: " << newValue << std::endl;
}
};
int main(int argc, char** argv)
{
PropertyOwner1 propertyOwner1;
PropertyOwner2 propertyOwner2;
PropertyObserver1 propertyObserver1;
PropertyObserver2 propertyObserver2;
// register observers
PropertyChangedDelegate<PropertyObserver1, int> delegate1(&propertyObserver1, &PropertyObserver1::OnPropertyOwner1Property1Changed);
propertyOwner1.property1ChangedEvent.add(&delegate1);
PropertyChangedDelegate<PropertyObserver2, float> delegate2(&propertyObserver2, &PropertyObserver2::OnPropertyOwner1Property2Changed);
propertyOwner1.property2ChangedEvent.add(&delegate2);
PropertyChangedDelegate<PropertyObserver1, bool> delegate3(&propertyObserver1, &PropertyObserver1::OnPropertyOwner2Property1Changed);
propertyOwner2.property1ChangedEvent.add(&delegate3);
PropertyChangedDelegate<PropertyObserver2, double> delegate4(&propertyObserver2, &PropertyObserver2::OnPropertyOwner2Property2Changed);
propertyOwner2.property2ChangedEvent.add(&delegate4);
propertyOwner1.property1(1);
propertyOwner1.property2(1.2f);
propertyOwner2.property1(true);
propertyOwner2.property2(3.4);
// unregister PropertyObserver1
propertyOwner1.property1ChangedEvent.remove(&delegate1);
propertyOwner2.property1ChangedEvent.remove(&delegate3);
propertyOwner1.property1(2);
propertyOwner1.property2(4.5f);
}
Output:
PropertyOwner1::property1(): property1_ set to 1
PropertyObserver1::OnPropertyOwner1Property1Changed():
new value is: 1
PropertyOwner1::property2(): property2_ set to 1.2
PropertyObserver2::OnPropertyOwner1Property2Changed():
new value is: 1.2
PropertyOwner2::property1(): property1_ set to 1
PropertyObserver1::OnPropertyOwner2Property1Changed():
new value is: 1
PropertyOwner2::property2(): property2_ set to 3.4
PropertyObserver2::OnPropertyOwner2Property2Changed():
new value is: 3.4
PropertyOwner1::property1(): property1_ set to 2
PropertyOwner1::property2(): property2_ set to 4.5
PropertyObserver2::OnPropertyOwner1Property2Changed():
new value is: 4.5
Each observer is registered with a particular property and when notified, each observer knows exactly who is the owner of the property and what's property's new value.