Elegant way to disconnect slot after first call
Asked Answered
L

1

11

Inside the constructor, there is a connection:

connect(&amskspace::on_board_computer_model::self(),
      SIGNAL(camera_status_changed(const amskspace::camera_status_t&)),
      this,
      SLOT(set_camera_status(const amskspace::camera_status_t&)));

And the method:

void camera_model::
set_camera_status(const amskspace::camera_status_t& status) {
  disconnect(&amskspace::on_board_computer_model::self(),
             SIGNAL(camera_status_changed(const amskspace::camera_status_t&)),
             this,
             SLOT(set_camera_status(const amskspace::camera_status_t&)));

  // do the job
}

And I'd like to disconnect this slot after the first call.

The question is: Is there a way to call the slot only once? Without the explicit disconnection? Like a single shot method? Is it possible?

Lancey answered 19/11, 2014 at 14:24 Comment(5)
What is the problem with using disconnect() function?Criticize
Why don't you want to use disconnect? Is there a specific reason?Ingham
Just to know if there is another way. For some developers, Call the disonnect after first calling is annoying, and I'd like to know if there is a way to do it.Lancey
I have a solution for a signal/slot without arguments, I'll try to find a solution for any arguments tomorrow. Do you have C++11/Qt5?Harlanharland
Actually my environment is a linux with C++ 4.4.7, and Qt4.8. But incoming solutions are welcome, also in Windows/C++11./Qt5Lancey
H
12

The core idea is to create a wrapper, a special "connect" which automatically disconnect the signal. This is usefull if you use a lot of "call me once" connections; otherwise I'd advice a Qobject::disconnect at the beginning of the slot.

This implementation works by creating 2 connections: a "normal" one, and one that disconnect & cleanup everything just after.

An implementation (using C++11/Qt 5, ):

template <typename Func1, typename Func2>
static inline QMetaObject::Connection weakConnect(
        typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
        typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot)
{

    QMetaObject::Connection conn_normal = QObject::connect(sender, signal, receiver, slot);

    QMetaObject::Connection* conn_delete = new QMetaObject::Connection();

    *conn_delete = QObject::connect(sender, signal, [conn_normal, conn_delete](){
        QObject::disconnect(conn_normal);
        QObject::disconnect(*conn_delete);
        delete conn_delete;
    });
    return conn_normal;
}

Caveats/Things to improve:

  • the cleanup occurs after calling the regular slot. If the regular slot causes the signal to be emitted again, the regular slot will be executed again (potentially causing an infinite recursion).
  • no proper way to disconnect, except by emitting the signal. (you can use QObject::disconnect, but that would cause a small memory leak)
  • relies on the order of execution of slots. Seems fine for now.
  • the naming

Tested using:

class A : public QObject
{
    Q_OBJECT
signals:
    void sig(int a);
};


class B : public QObject
{
    Q_OBJECT
public:
    B(int b) : QObject(), b_(b) {}
    int b() const { return b_; }
public slots:
    void slo(int a) { qDebug() << "\tB :" << b_ << "a:" << a;  }
private:
    int b_;
};

and

A a1;
A a2;

B b10(10);
B b20(20);

weakConnect(&a1, &A::sig, &b10, &B::slo);
weakConnect(&a1, &A::sig, &b20, &B::slo);
weakConnect(&a2, &A::sig, &b20, &B::slo);

qDebug() << "a1 :"; emit a1.sig(1);// Should trigger b10 and b20 slo
qDebug() << "a2 :"; emit a2.sig(2);// Should trigger b20 slo
qDebug() << "a1 :"; emit a1.sig(3);// Should do nothing
qDebug() << "a2 :"; emit a2.sig(4);// Should do nothing

Test code output:

a1 :
    B : 10 a: 1
    B : 20 a: 1
a2 :
    B : 20 a: 2
a1 :
a2 :

Getting rid of C++11/Qt5 (I don't have Qt 4.8/GCC4.4.7, so not tested with those) According to the doc, Qt 4.8 doesn't have a connect to function, so I'm using a wrapper:

class ConnectJanitor : public QObject
{
    Q_OBJECT
public slots:
    void cleanup()
    {
        QObject::disconnect(conn_normal_);
        QObject::disconnect(*conn_delete_);
        delete conn_delete_;
        delete this;
    }
public:
    static ConnectJanitor* make(QMetaObject::Connection conn_normal,
                                QMetaObject::Connection* conn_delete)
    {
        return new ConnectJanitor(conn_normal, conn_delete);
    }
private:
    ConnectJanitor(QMetaObject::Connection conn_normal,
                   QMetaObject::Connection* conn_delete) :
        QObject(0) , conn_normal_(conn_normal), conn_delete_(conn_delete) {}

    ConnectJanitor(const ConnectJanitor&); // not implemented
    ConnectJanitor& operator=(ConnectJanitor const&);


    QMetaObject::Connection conn_normal_;
    QMetaObject::Connection* conn_delete_;
};

(I'm making the ConnectJanitor's constructor private because the instance self-destructs (delete this))

and for weakConnect:

static inline QMetaObject::Connection weakConnect(const QObject * sender, const char * signal, const QObject * receiver, const char * slot)
{
    QMetaObject::Connection conn_normal = QObject::connect(sender, signal, receiver, slot);
    QMetaObject::Connection* conn_delete = new QMetaObject::Connection();
    *conn_delete = QObject::connect(sender, signal, ConnectJanitor::make(conn_normal, conn_delete), SLOT(cleanup()));
    return conn_normal;
}

If you need to manually break the connections, I suggest to have weakConnect() returning the ConnectJanitor's pointer.

Harlanharland answered 20/11, 2014 at 10:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.