Qt 是否可以定义一个函数指针来代替信号?

Qt is it possible to define a function pointer in place of a signal?

我有很多信号,它们都具有相同的参数但执行不同的功能。

所有信号的连接和断开代码都相同,信号连接到的插槽处理程序也是如此。

而不是一遍又一遍地编写这段代码。我想使用函数指针或类似的东西来分配给信号,然后有一个执行连接或断开连接的公共代码块。

以下代码只是为了说明我所描述的内容,它无效且无法编译。

    void (*pfnSignal)(quint8, QString);
    switch( eSigID ) {
    case SIGNAL_A:
        pfnSignal = signalA; 
        break;
    case SIGNAL_B:
        pfnSignal = signalB;
        break;
    default:
        pfnSignal = NULL;           
    }
    if ( pfnSignal != NULL ) {
      QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
    }

在 Qt5 中,这很容易完成,因为它允许使用 new pointer to member function syntax.

进行连接
// Using decltype to avoid figuring out the ugly pointer-to-member-function syntax. 
// Assumes all signals have the same arguments.    
decltype<&ThatClass::someSignal> pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
    pfnSignal = &ThatClass::signalA; 
    break;
case SIGNAL_B:
    pfnSignal = &ThatClass::signalB;
    break;          
}

if (pfnSignal) {
    connect(pobjRef, pfnSignal, this, &ThisClass::handler);
}

但实际上,这对于 Qt4 甚至是可能的,因为 SIGNAL 宏的类型是 const char*.

const char *pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
    pfnSignal = SIGNAL(signalA(quint8, QString)); 
    break;
case SIGNAL_B:
    pfnSignal = SIGNAL(signalB(quint8, QString)); 
    break;         
}
if (pfnSignal) {
  QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
}

实际上,Thomas McGuire 比我快。 (该死的。)不过,我想添加这个答案是因为:

  1. 提供了完整的示例。

  2. 它使用仿函数而不是 object/member 信号处理程序的函数指针。

因此,它可能是 Thomas McGuire 的答案的补充。

在 Qt 5 之前,信号由 char* 描述,这应该很容易处理。因此,我假设您的问题是关于自 Qt 5 以来的新 API。

如果您使用正确的方法指针类型,这应该也能正常工作。我为 QPushButton and QCheckBox for demonstration because both are derived from QAbstractButton 这样做,它又具有两个具有相同签名的信号。恕我直言,信号的相等签名对于您的解决方案是强制性的。

#include <QtWidgets>

enum SigType { None, Click, Toggle };

template <typename FUNCTOR>
void installSignalHandler(
  QAbstractButton *pQBtn,
  SigType sigType,
  FUNCTOR sigSlot)
{
  void (QAbstractButton::*pSignal)(bool) = nullptr;
  switch (sigType) {
    case Click: pSignal = &QAbstractButton::clicked; break;
    case Toggle: pSignal = &QAbstractButton::toggled; break;
  }
  if (pSignal) QObject::connect(pQBtn, pSignal, sigSlot);
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version: " << QT_VERSION_STR;
  // main application
  QApplication app(argc, argv);
  // setup GUI
  QWidget qWin;
  QVBoxLayout qVBox(&qWin);
  QPushButton qBtn1("Button 1 -> Click");
  qVBox.addWidget(&qBtn1);
  QPushButton qBtn2("Button 2 -> Toggle");
  qVBox.addWidget(&qBtn2);
  QPushButton qBtn3("Button 3 -> None");
  qVBox.addWidget(&qBtn3);
  QCheckBox qTgl1("Toggle 1 -> Click");
  qVBox.addWidget(&qTgl1);
  QCheckBox qTgl2("Toggle 2 -> Toggle");
  qVBox.addWidget(&qTgl2);
  QCheckBox qTgl3("Toggle 3 -> None");
  qVBox.addWidget(&qTgl3);
  qWin.show();
  // install signal handlers
  installSignalHandler(&qBtn1, Click,
    [](bool) { qDebug() << "Button 1 received clicked."; });
  installSignalHandler(&qBtn2, Toggle,
    [](bool) { qDebug() << "Button 2 received toggled."; });
  installSignalHandler(&qBtn3, None, // will be actually never called
    [](bool) { qDebug() << "Button 3 received none."; });
  installSignalHandler(&qTgl1, Click,
    [](bool) { qDebug() << "CheckBox 1 received clicked."; });
  installSignalHandler(&qTgl2, Toggle,
    [](bool) { qDebug() << "CheckBox 2 received toggled."; });
  installSignalHandler(&qTgl2, None, // will be actually never called
    [](bool) { qDebug() << "CheckBox 3 received none."; });
  // run-time loop
  return app.exec();
}

在 Windows 10(64 位)上使用 VisualStudio 和 Qt 5.6 进行编译和测试:

C++11 允许您编写非常简洁的 Qt 代码。

  1. 利用 range-based for loops 迭代指针。这些可以是指向小部件的指针、指向方法的指针等:

    for (auto signal : {&Class::signal1, &Class:signal2})
       QObject::connect(sender, signal, receiver, slot);
    
  2. 利用 lambda expressions 捕获常量参数值:

    auto const cMySlot = [&](void (Sender::*signal)(int)){
      QObject::connect(sender, signal, receiver, slot);
    

然后:

for (auto signal : {&Class::signal1, &Class:signal2}) cMySlot(signal);

完整示例:

// https://github.com/KubaO/Whosebugn/tree/master/questions/signals-simpler-43631464
#include <QtWidgets>
#include <initializer_list>

class Receiver : public QLabel {
   Q_OBJECT
public:
   Receiver(QWidget * parent = {}) : QLabel{parent} {}
   Q_SLOT void intSlot(int val) {
      setText(QStringLiteral("int = %1").arg(val));
   }
};

class Sender : public QWidget {
   Q_OBJECT
   QFormLayout m_layout{this};
   QPushButton btn1{"Send 1"}, btn2{"Send 5"}, btn3{"Send 10"};
public:
   Sender(QWidget * parent = {}) : QWidget{parent} {
      m_layout.setMargin(1);
      for (auto w : {&btn1, &btn2, &btn3}) m_layout.addWidget(w);
      auto const clicked = &QPushButton::clicked;
      connect(&btn1, clicked, this, [this]{ emit signal1(1); });
      connect(&btn2, clicked, this, [this]{ emit signal2(5); });
      connect(&btn3, clicked, this, [this]{ emit signal3(10); });
   }
   Q_SIGNAL void signal1(int);
   Q_SIGNAL void signal2(int);
   Q_SIGNAL void signal3(int);
};

using Widgets = std::initializer_list<QWidget*>;

int main(int argc, char **argv)
{
   QApplication app{argc, argv};
   QWidget win;
   QVBoxLayout layout{&win};
   Sender sender;
   Receiver receiver;
   for (auto w : Widgets{&sender, &receiver}) layout.addWidget(w);

   // Factor out connection
   auto const cIntSlot = [&](void (Sender::*signal)(int)){
      QObject::connect(&sender, signal, &receiver, &Receiver::intSlot);
   };
   // Factor out connection on a list
   for (auto signal : {&Sender::signal1, &Sender::signal2, &Sender::signal3})
      cIntSlot(signal);

   win.show();
   return app.exec();
}
#include "main.moc"