Qt 5 引入了信号槽的新语法:使用函数指针能够获得编译期的类型检查。使用我们在中设计的类,我们来看看其基本语法:

main()函数中,我们使用connect()函数将newspaper对象的newPaper()信号与reader对象的receiveNewspaper()槽函数联系起来。当newspaper发出这个信号时,reader相应的槽函数就会自动被调用。这里我们使用了取址操作符,取到Newspaper::newPaper()信号的地址,同样类似的取到了函数地址。编译器能够利用这两个地址,在编译期对这个连接操作进行检查,如果有个任何错误(包括对象没有这个信号,或者信号参数不匹配等),编译时就会发现。

如果信号有重载,比如我们向Newspaper类增加一个新的信号:

  1. void newPaper(const QString &name, const QDate &date);

此时如果还是按照前面的写法,编译器会报出一个错误:由于这个函数(注意,信号实际也是一个普通的函数)有重载,因此不能用一个取址操作符获取其地址。回想一下 Qt 4 中的处理。在 Qt 4 中,我们使用SIGNALSLOT两个宏来连接信号槽。如果有一个带有两个参数的信号,像上面那种,那么,我们就可以使用下面的代码:

  1. QObject::connect(&newspaper, SIGNAL(newPaper(QString, QDate)),
  2. &reader, SLOT(receiveNewspaper(QString, QDate)));

注意,我们临时增加了一个receiveNewspaper()函数的重载,以便支持两个参数的信号。在 Qt 4 中不存在我们所说的错误,因为 Qt 4 的信号槽连接是带有参数的。因此,Qt 能够自己判断究竟是哪一个信号对应了哪一个槽。

这样,我们使用了函数指针newspaperNameDate声明一个带有QStringQDate两个参数,返回值是 void 的函数,将该函数作为信号,与Reader::receiveNewspaper()槽连接起来。这样,我们就回避了之前编译器的错误。归根结底,这个错误是因为函数重载,编译器不知道要取哪一个函数的地址,而我们显式指明一个函数就可以了。

如果你觉得这种写法很难看,想像前面一样写成一行,当然也是由解决方法的:

  1. QObject::connect(&newspaper,
  2. (void (Newspaper:: *)(const QString &, const QDate &))&Newspaper::newPaper,
  3. &Reader::receiveNewspaper);

这是一种换汤不换药的做法:我们只是声明了一个匿名的函数指针,而之前我们的函数指针是有名字的。不过,我们并不推荐这样写,而是希望以下的写法:

  1. QObject::connect(&newspaper,
  2. static_cast<void (Newspaper:: *)(const QString &, const QDate &)>(&Newspaper::newPaper),
  3. &reader,
  4. &Reader::receiveNewspaper);

对比上面两种写法。第一个使用的是 C 风格的强制类型转换。此时,如果你改变了信号的类型,那么你就会有一个潜在的运行时错误。例如,如果我们把(const QString &, const QDate &)两个参数修改成,C 风格的强制类型转换就会失败,并且这个错误只能在运行时发现。而第二种则是 C++ 推荐的风格,当参数类型改变时,编译器会检测到这个错误。

注意,这里我们只是强调了函数参数的问题。如果前面的对象都错了呢?比如,我们写的newspaper对象并不是一个Newspaper,而是Newspaper2?此时,编译器会直接失败,因为connect()函数会去寻找sender->*signal,如果这两个参数不满足,则会直接报错。

就是这种情况。

然而,有一种情况,槽函数的参数可以比信号的多,那就是槽函数的参数带有默认值。比如,我们的NewspaperReader有下面的代码:

  1. // Newspaper
  2. signals:
  3. // Reader
  4. void receiveNewspaper(const QString &name, const QDate &date = QDate::currentDate());

虽然Reader::receiveNewspaper()的参数数目比Newspaper::newPaper()多,但是由于后面一个参数带有默认值,所以该参数不是必须提供的。但是,如果你按照前面的写法,比如如下的代码:

  1. QObject::connect(&newspaper,
  2. static_cast<void (Newspaper:: *)(const QString &)>(&Newspaper::newPaper),
  3. &reader,
  4. static_cast<void (Reader:: *)(const QString &, const QDate & =QDate::currentDate())>(&Reader::receiveNewspaper));

你会得到一个断言错误:

我们不能在函数指针中使用函数参数的默认值。这是 C++ 语言的限制:参数默认值只能使用在直接地函数调用中。当使用函数指针取其地址的时候,默认参数是不可见的!

  1. QObject::connect(&newspaper,
  2. [=](const QString &name) { /* Your code here. */ });