Соединяем Qt-сигнал с простой функцией и лямбда

от автора

У многих программистов, работающих с Qt, наверняка возникало навязчивое желание соединить сигнал, посылаемый неким наследником QObject, c простой функцией, не являющейся слотом или даже членом некоторого класса. Однако если весь проект построен на объектах (как обычно и бывает), да и все они наследуются от QObject, то добавить функцию-слот куда надо не составит труда. А если нет? Если вы, например, из экономии памяти (или по другим соображениям) не хотите наследовать ваш класс от QObject, или же действие слота занимает всего 1 строчку и было бы проще и удобнее написать его в виде лямбда-выражения? Или вы по ряду причин хотите по сигналу вызывать одиночную фунцию, не являющуюся членом класса?

Столкнувшись с этой проблемой, я решил написать класс, который позволяет соединять сигнал не только с функцей-слотом, а ещё и с самой обычной функцией, а при поддержке С++11 — ещё и с лямбда-выражением.

Пишем класс SmartConnect

Для начала — что должен делать класс? Он должен наследоваться от QObject и хранить ссылку на нашу функцию. Т.е. мы связываем сигнал какого-либо класса с некоторым слотом SmartConnect, а уже SmartConnect хранит ссылку на нашу независимую функцию или лямбда-выражение, и вызывает его по своему слоту.

Самым удобным решением будет перегрузить конструктор SmartConnect — для ссылки на разные функции. Пускай он для начала работает двумя типами сигналов — которые в аргументе передают void, а также те, которые передают QString. Создадим файл smartconnect.h:

#include <QtCore>
 
class SmartConnect : public QObject
{
  Q_OBJECT
  void (*pVoidFunc)(void);
  void (*pQStringFunc)(QString);
public:
  SmartConnect(QObject* sender,const char* signal,void (*pFunc)(void));
  SmartConnect(QObject* sender,const char* signal,void (*pFunc)(QString));
private slots:
  void voidSlot();
  void QStringSlot(QString str);
};

Затем — собственно smartconnect.cpp:

#include <QtCore>
#include "smartconnect.h"
 
SmartConnect::SmartConnect(QObject *sender, const char *signal, void (*pFunc)()){
  pVoidFunc = pFunc;
  QObject::connect(sender,signal,this,SLOT(voidSlot()));
}
 
SmartConnect::SmartConnect(QObject *sender, const char *signal, void (*pFunc)(QString)){
  pQStringFunc = pFunc;
  QObject::connect(sender,signal,this,SLOT(QStringSlot(QString)));
}
 
void SmartConnect::voidSlot(){
  pVoidFunc();
}
 
void SmartConnect::QStringSlot(QString str){
  pQStringFunc(str);
}

Как видите, класс действительно smart — в зависимости от вызванного конструктора сам выбирает нужный внутренний слот и соединяется с ним, а также сохраняет ссылку на нашу функцию. При желании мы можем сделать поддержку сигналов с любыми аргументами — просто добавить ссылку на функцию с этими аргументами, слот, который их принимает и конструктор. А вот внешне использовать класс будет всё также просто.

Пишем демонстрационный пример

Теперь создадим файл main.cpp:

#include <QtGui>
#include "smartconnect.h"
 
void onClick(){
  qDebug()<<"Hello from void onClick()";
}
 
int main(int argc, char* argv[]){
  QApplication app(argc, argv);
 
  QPushButton button1("button1");
  button1.show();
  SmartConnect smartConnect1(&button1,SIGNAL(clicked()),onClick);
 
  QPushButton button2("button2");
  SmartConnect smartConnect2(&button2,SIGNAL(clicked()),[](){qDebug()<<"Hello from lambda";});
  button2.show();
 
  return app.exec();
}

Здесь мы создаём 2 кнопки, одна из которых соединяется с функцией не-слотом, другая — с лямбда-выраженим — и при клике по ним выводятся соответствующие сообщения. Теперь давайте создадим pro-файл, не забыв подключить С++11. Сразу скажу, что для этого потребуется новая версия Qt, собранная под такой же компилятор, поддерживающий С++11. Иначе — пример с лямбдой работать не будет. Создадим файл main.pro:

QT += gui
TEMPLATE = app
CONFIG += release
SOURCES += main.cpp
           smartconnect.cpp
HEADERS += smartconnect.h
CONFIG   += console
QMAKE_CXXFLAGS += std=gnu++11

И, наконец скомпилируем и соберём наш пример:

qmake main.pro o Makefile 
make

При желании, класс можно усовершенствовать, например добавить в него disconnect, если нужно. Как видите — использование класса просто и удобно, в некоторых случаях он может очень пригодится и реально упростить код.

ссылка на оригинал статьи http://habrahabr.ru/post/171835/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *