code_fodder
code_fodder

Reputation: 16321

Connect one object to many objects of same type

So I have the following setup:

|---- Device[1] Controller ---+---- Device[2] |---- Device[3] | : |---- Device[x]

Each object is in its own thread.

For communications from Deivce[x] to controller this is relatively easy, I just connect all the device signals to a slot in the controller and pass the device index as a parameter so the controller can identify which device.

However, I am unsure how is best to do the same in the other direction (controller to device). The best I have come up with is a similar scheme, where I connect a signal in controller to a slot in each of the devices. I can still pass an index so if the messages is directed at Device[1], then Device[2] and Device[3] can ignore it. However, is this a huge overhead in terms of data repetition? - i.e. is the data sent three times?

Is there a better way?

edit 1 I have edited the example to show that I can have any number of devices.. its not hard-coded number, so if I need a signal/slot for each device the signals will need to be created dynamically.

edit 2 It seems you can call methods directly using something like: QMetaObject::invokeMethod(devices[x], "handleMessage", Qt::QueuedConnection, Q_ARG(QByteArray msgData)));

This allows me to invoke a slot on any object (queued, so its thread safe) and pass my data.... but it feels like I am breaking fundamental Qt slots/signals methodology if I do this.

Upvotes: 0

Views: 136

Answers (2)

code_fodder
code_fodder

Reputation: 16321

So I have not found much on the internet about this issue. There are some possibilities I have seen people try to use/answer the question with:

1. QSignalMapper

This has its limitations which I have commented on in ram's answer.

2. Invoke Target Slot Directly

You can invoke the target slot directly (yes across thread boundary!), this can be done as shown below (just showing this for completeness):

QMetaObject::invokeMethod(devices[x], "handleMessage", Qt::QueuedConnection, Q_ARG(QByteArray msgData)));

Where:

  • Devices[x] is a pointer to the device object
  • "handleMessage" is the name of the slot function
  • Queued connection is used to make sure its thread safe and not called in the calling thread.
  • Q_ARG(...) is an argument that is passed to the slot, in this case a QByteArray called msgData. Note: you can have multiple of these.

This works fine, but it breaks the whole methodology of slots/signals, because we are invoking a slot in a foreign object without a signal or connection... effectively punching a hole through the wall and grabbing what we want - so it also breaks the encapsulation principals too (not the mention thread boundary).

3. Dynamic Slot Creation

I read this in the qt documentation here, about halfway down, but it looks like horrible code to have to write/maintain so I did not even try this.

My Proposed Solution

So finally I decided I have to dream something up. Because I only know how many devices I will have at run time, creating signals dynamically as each device registers is appealing, but as described above, not very nice.

However we can instantiate objects dynamically, where the object contains a signal that can be used to map one-to-one to a device. Effectively creating a "postbox" object where its signal can be connected to the equivalent device at run-time. The setup is shown below:

|-------------------------| | | | Postbox[1]--+------Device[1] | Controller Postbox[2]--+------Device[2] | Postbox[3]--+------Device[3] | | : | Postbox[x]--+------Device[x] | | |-------------------------|

Where the controller object contains an array (or vector) of postbox objects. For each device that registers with the controller it creates a new instance of postbox, connects its signal to the devices slot and then adds the postbox to the array. Then to post a message to, for example device[3], the controller simply calls a function like postboxes[3].postmessage(msgData);, the postmessage function emits the signal that is connected to the device[3].

As far as I can tell, this is the only "correct/simple" way to do this because qt slots/signals does not seem to be setup to do message routing as such. Please someone correct me if I am wrong!

Upvotes: 1

ramtheconqueror
ramtheconqueror

Reputation: 1964

Use QSignalMapper.

Controller::Controller()
{
    QSignalMapper* signalMapper = new QSignalMapper(this);
    for (int i = 0; i < deviceCount; ++i) {
        Device* d = new Device(...);
        connect(d, SIGNAL(somethingHappened)), signalMapper, SLOT(map()));
        signalMapper->setMapping(d, i);
    }

    connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(handleDevice(int)));
}

void Controller::handleDevice(int id)
{
 .....
}

More here: http://doc.qt.io/qt-5/qsignalmapper.html

Upvotes: 0

Related Questions