Элегантный способ отключения слота после первого звонка

Внутри конструктора есть подключение:

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&)));

И метод:

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
}

И я хотел бы отключить этот слот после первого звонка.

Вопрос: есть ли способ вызвать слот только один раз? Без явного отключения? Нравится метод одиночного выстрела? Является ли это возможным?


person Etore Marcari Jr.    schedule 19.11.2014    source источник
comment
В чем проблема с использованием функции disconnect()?   -  person vahancho    schedule 19.11.2014
comment
Почему вы не хотите использовать disconnect? Есть ли конкретная причина?   -  person Robert    schedule 19.11.2014
comment
Просто чтобы узнать, есть ли другой способ. Для некоторых разработчиков вызов отключения после первого вызова раздражает, и я хотел бы знать, есть ли способ сделать это.   -  person Etore Marcari Jr.    schedule 19.11.2014
comment
У меня есть решение для сигнала/слота без аргументов, завтра попробую найти решение для любых аргументов. У вас есть С++ 11/Qt5?   -  person Armaghast    schedule 19.11.2014
comment
На самом деле моя среда — это Linux с C++ 4.4.7 и Qt4.8. Но приветствуются поступающие решения, в том числе в Windows/C++11./Qt5.   -  person Etore Marcari Jr.    schedule 19.11.2014


Ответы (1)


Основная идея состоит в том, чтобы создать оболочку, специальный «коннект», который автоматически отключает сигнал. Это полезно, если вы используете много соединений «позвони мне один раз»; в противном случае я бы посоветовал Qobject::disconnect в начале слота.

Эта реализация работает, создавая 2 соединения: «обычное» и одно, которое отключает и очищает все сразу после этого.

Реализация (с использованием 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;
}

Предостережения/Что нужно улучшить:

  • очистка происходит после вызова обычного слота. Если обычный слот вызывает повторную передачу сигнала, то обычный слот будет выполняться снова (потенциально вызывая бесконечную рекурсию).
  • нет правильного способа отключиться, кроме как путем подачи сигнала. (вы можете использовать QObject::disconnect, но это вызовет небольшую утечку памяти)
  • зависит от порядка выполнения слотов. Кажется, сейчас все в порядке.
  • наименование

Проверено с использованием:

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_;
};

а также

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

Вывод тестового кода:

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

Избавление от C++11/Qt5 (у меня нет Qt 4.8/GCC4.4.7, поэтому я не тестировал их) Согласно документу, Qt 4.8 не имеет функции подключения к , поэтому я использую обертку:

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_;
};

(Я делаю конструктор ConnectJanitor закрытым, потому что экземпляр самоуничтожается (delete this))

и для 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;
}

Если вам нужно вручную разорвать соединения, я предлагаю, чтобы weakConnect() возвращал указатель ConnectJanitor.

person Armaghast    schedule 20.11.2014