New Signal Slot Syntax/Zh
此页面用于描述新的信号和插槽语法。该功能现已随Qt 5一起发布。
- 基于字符串的连接和基于函子的连接之间的区别 (官方文档)
- 简介 (Woboq 博客)
- 实现 (Woboq 博客)
注意:这是对旧的基于字符串的语法的补充,该语法仍然有效。
在Qt 5中进行连接
在Qt 5中有几种连接信号的方法。
旧语法
Qt 5继续支持基于旧的基于字符串的语法,用于连接QObject或从QObject继承的任何类(包括QWidget)中定义的信号和槽。
connect( sender, SIGNAL( valueChanged( QString, QString ) ), receiver, SLOT( updateValue( QString ) ) );
新增:连接到QObject成员
这是Qt 5连接两个QObject并传递非字符串对象的新方法:
connect( sender, &Sender::valueChanged, receiver, &Receiver::updateValue );
优点
- 编译时检查是否存在信号和插槽,类型,或者是否缺少Q_OBJECT。
- 参数可以通过typedef定义,也可以使用不同的名称空间说明符,并且可以使用。
- 如果存在隐式转换(例如,从QString到QVariant),则可以自动转换类型。
- 可以连接到QObject的任何成员函数,而不仅仅是插槽。
缺点
- 更复杂的语法?(您需要指定对象的类型)
- 过载情况下的语法非常复杂吗?(见 下文)
- 不再支持插槽中的默认参数。
新增:连接到简单功能
新语法甚至可以连接到函数,而不仅仅是QObjects:
connect( sender, &Sender::valueChanged, someFunction );
优点
- 可以与std::bind一起使用::
connect( sender, &Sender::valueChanged, std::bind( &Receiver::updateValue, receiver, "senderValue", std::placeholders::_1 ) );
- 可以与C++11 lambda表达式一起使用:
connect( sender, &Sender::valueChanged, [=]( const QString &newValue ) { receiver->updateValue( "senderValue", newValue ); } );
缺点
- 销毁“接收器”时不会自动断开连接,因为它是没有QObject的函子。但是,从5.2开始,有一个重载会添加一个“上下文对象”。当该对象被销毁时,连接断开(上下文也用于线程亲缘关系:将在用作上下文的对象的事件循环的线程中调用lambda)。
在Qt 5中断开连接
如您所料,在Qt 5中如何终止连接也有一些变化。
旧方法
您可以使用旧方法(使用SIGNAL,SLOT)断开连接,但前提是
- 您使用旧方法进行连接,或者
- 如果要使用通配符将所有插槽与给定信号断开连接
与函数指针对称
disconnect( sender, &Sender::valueChanged, receiver, &Receiver::updateValue );
仅在通过函数指针与对称调用连接时才有效(或者也可以对通配符使用0)。特别是,不适用于静态函数,函子或lambda函数。
使用QMetaObject::Connection的新方法n
QMetaObject::Connection m_connection; // … m_connection = QObject::connect( /* … */ ); // … QObject::disconnect( m_connection );
适用于所有情况,包括lambda函数或函子。
异步变得更容易
使用C++11可以保持代码内联
void doYourStuff( const QByteArray &page ) { QTcpSocket *socket = new QTcpSocket; socket->connectToHost( "qt.io", 80 ); QObject::connect( socket, &QTcpSocket::connected, [socket, page]() { socket->write( QByteArray( "GET " + page + "" ) ); } ); QObject::connect( socket, &QTcpSocket::readyRead, [socket]() { qDebug() << "GOT DATA " << socket->readAll(); } ); QObject::connect( socket, &QTcpSocket::disconnected, [socket]() { qDebug() << "DISCONNECTED "; socket->deleteLater(); } ); QObject::connect( socket, static_cast<void ( QTcpSocket::* )( QAbstractSocket::SocketError )>( &QAbstractSocket::error ), [socket]( QAbstractSocket::SocketError ) { qDebug() << "ERROR " << socket->errorString(); socket->deleteLater(); } ); }
这是一个QDialog,无需重新输入eventloop,并将代码保留在其所属位置:
void Doc::saveDocument() { QFileDialog *dlg = new QFileDialog(); dlg->open(); QObject::connect( dlg, &QDialog::finished, [dlg, this]( int result ) { if ( result ) { QFile file( dlg->selectedFiles().first() ); // … } dlg->deleteLater(); } ); }
使用QHttpServer另一个示例 :http://pastebin.com/pfbTMqUm
错误报告
使用GCC测试。
幸运的是,像Qt Creator这样的IDE可以简化函数命名
类定义中缺少Q_OBJECT
#include <QtCore> class Goo : public QObject { Goo() { connect( this, &Goo::someSignal, this, &QObject::deleteLater ); } signals: void someSignal(); };
qobject.h: In member function 'void QObject::qt_check_for_QOBJECT_macro(const T&&) const [with T = Goo]': qobject.h:535:9: instantiated from 'static typename QtPrivate::QEnableIf<((int) (QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int) (QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type QObject::connect(const typename QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*, Func2, Qt::ConnectionType) [with Func1 = void (Goo::*)(), Func2 = void (QObject::*)(), typename QtPrivate::QEnableIf<((int)(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int) (QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type = void*, typename QtPrivate::FunctionPointer<Func>::Object = Goo, typename QtPrivate::FunctionPointer<Func2>::Object = QObject]' main.cc:4:68: instantiated from here qobject.h:353:5: error: void value not ignored as it ought to be make: '''* [main.o] Error 1
类型不匹配
#include <QtCore> class Goo : public QObject { Q_OBJECT public: Goo() { connect( this, &Goo::someSignal, this, &Goo::someSlot1 ); // Error connect( this, &Goo::someSignal, this, &Goo::someSlot2 ); // Works } signals: void someSignal( QString ); public: void someSlot1( int ); void someSlot2( QVariant ); };
qobject.h: In static member function 'static typename QtPrivate::QEnableIf<((int) (QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int) (QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type QObject::connect(const typename QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*, Func2, Qt::ConnectionType) [with Func1 = void (Goo::*)(QString), Func2 = void (Goo::*)(int), typename QtPrivate::QEnableIf<((int)(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int) (QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type = void*, typename QtPrivate::FunctionPointer<Func>::Object = Goo, typename QtPrivate::FunctionPointer<Func2>::Object = Goo]': main.cc:6:62: instantiated from here qobject.h:538:163: error: no type named 'IncompatibleSignalSlotArguments' in 'struct QtPrivate::CheckCompatibleArguments<QtPrivate::List<QString, void>, QtPrivate::List<int, void>, true>' qobject.h: In static member function 'static void QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::call (QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::Function, Obj*, void*) [with Args = QtPrivate::List<QString, void>, Obj = Goo, Ret = void, Arg1 = int, QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::Function = void (Goo::*)(int)]': qobject.h:501:13: instantiated from 'void QObject::QSlotObject<Func, Args>::call(QObject*, void**) [with Func = void (Goo::*)(int), Args = QtPrivate::List<QString, void>, QObject = QObject]' main.cc:14:2: instantiated from here qobject.h:109:13: error: cannot convert 'QtPrivate::RemoveRef<QString>::Type' to 'int' in argument passing make: *** [main.o] Error 1
悬而未决的问题
插槽中的默认参数
如果您有这样的代码:
class A : public QObject { Q_OBJECT public slots: void someSlot(int foo = 0); };
旧方法允许您将该插槽连接到没有参数的信号。但是我不知道模板代码中的函数是否具有默认参数。因此此功能被禁用。
如果插槽中的参数比信号中的参数多,则有一种方法可以回溯到旧方法。但是,这是非常不一致的,因为旧方法不执行类型检查或类型转换。它已从已合并的修补程序中删除。
重载
如您在上面的示例中可能看到的那样,由于error具有重载,因此连接到QAbstractSocket::error并不是很漂亮,并且获取重载函数的地址需要显式转换,例如,以前按如下方式进行的连接:
connect(mySpinBox, SIGNAL(valueChanged(int)), mySlider, SLOT(setValue(int));
不能简单地转换为:
connect( mySpinBox, &QSpinBox::valueChanged, mySlider, &QSlider::setValue );
...因为QSpinBox 具 有两个名为valueChanged()且具有不同参数的信号。相反,新代码需要为:
connect( mySpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), mySlider, &QSlider::setValue );
不幸的是,在此处使用显式强制转换会导致多种类型的错误流失到编译器中。添加临时变量赋值可保留以下编译时检查:
void (QSpinBox::* mySignal)(int) = &QSpinBox::valueChanged; connect( mySpinBox, mySignal, mySlider, &QSlider::setValue );
某些宏可能会有所帮助(使用C++11或typeof扩展名)。Qt 5.7中引入了基于模板的解决方案: qOverload
最好的做法可能是建议不要重载信号或槽...
…但是我们在过去的Qt的次要版本中一直添加重载,因为获取函数的地址不是我们支持的用例。但是现在在不破坏源兼容性的情况下这将是不可能的。
断开连接
QMetaObject::Connection是否应该具有disconnect()函数?
另一个问题是,如果我们使用采用闭包的语法,则闭包中的某些对象不会自动断开连接。可以在断开连接中添加对象列表,或者添加诸如QMetaObject::Connection::require之类的新功能。
// Solution 1 auto c = connect( sender, &Sender::valueChanged, [=]( const QString &newValue ) { receiver->updateValue( "senderValue", newValue ); }, QList<QObject> { receiver } ); // Solution 2 (needs a new definition of QMetaObject::Connection::require) c.require( receiver );
回调
QHostInfo::lookupHost(直到Qt 5.9)或QTimer::singleShot(直到Qt 5.4)或QFileDialog::open之类的函数带有QObject接收器和char*槽。这不适用于新方法。如果要使用回调C++方式,则应使用std::function, 但是我们不能在ABI中使用STL类型,因此应执行QFunction来复制std::function。无论如何,这与QObject连接无关。