Home > Enterprise >  Prevent direct call to slot, prefer connect which led to call on another thread
Prevent direct call to slot, prefer connect which led to call on another thread

Time:06-12

In the Qt world we can put heavy duty tasks on separated classes and make the function calls using signal/slot mechanism which then, if designed appropriately and implemented accordingly will led to a call on a separated thread.

However with all these, seems to me, one can still go and (inappropriately) make the function calls directly. That will led to calls on the same thread.

So I'm wondering if there are ideas on how to write the interfaces to enforce the calls using signal/slot mechanism? Or, all we can do is hope for the best.

CodePudding user response:

Once you make a slot (a method) public, then you cannot prevent it being called directly. So the only solution is to make the slot private. But then you can only establish the connection from within the class. And therefore you need to pass the instance of the emitting class, e.g. in constructor.

class SomeReceiver: public QObject
{
    Q_OBJECT

public:
    explicit SomeReceiver(SomeEmitter *emitter, QObject *parent = nullptr) : QObject(parent)
    {
        connect(emitter, &SomeEmitter::someSignal, this, &SomeReceiver::someSlot);
    }

private:
    void someSlot() // you cannot call it directly from outside, it is private
    {
        // some stuff here ...
    }
}

(Alternatively, instead of establishing the connection in a constructor, you can make a setter function which would take a pointer to emitter instance and establish the connection. But then there is a danger of this setter being called twice etc. So I would prefer using the constructor...)

You can use it like this:

auto emitter = new SomeEmitter();
auto receiver = new SomeReceiver(emitter);

The code above ensures calling via signals and slots.

And if you want the code to be executed in a secondary thread, then just move the receiver to that thread.

receiver->moveToThread(&someThread);

... and then every time you emit emitter's signal, the conected slot will always be executed in the thread. If you do not move it to secondary thread, it will be executed in the main/GUI thread.

To enforce you move this object to a thread, then you can also move it in the constructor (either by passing also a thread pointer to constructor or by creating a thread in it - but then you need to take care of all the thread destructuring). I would strongly not recommend this, this is extremely ugly design.

PS: If you want to really enforce that some code is run in a thread, then you should inherint from QThread and override its run() method. This is ALWAYS executed in the thread.

CodePudding user response:

You can create a thin wrapper object that proxies direct calls into emitting signals that trigger calling slots of an actual worker in a separate thread. Here's a quick and dirty example:

class Task : public QObject {
    Q_OBJECT
public:
    void doStuff();
};

class TaskRunner {
    Task* m_task;
    QThread* m_thread;
public:
    TaskRunner() {
        m_thread = new QThread;
        m_task = new Task;
        connect(this, &TaskRunner::doStuffRequest, m_task, &Task::doStuff);
        m_task->moveToThread(m_thread);
        m_thread->start();
    }

    void doStuff() {
        emit doStuffRequest();
    }

signals:
    void doStuffRequest();
}

You can make a constructor of Task object private and make TaskRunner friend in Task, so users can't instantiate Task object directly.

For a real-world example I would probably make a runner QObject-inherited as well. Also don't forget to stop a thread properly and do a memory cleanup in a destructor.

  • Related