New Signal Slot Syntax/Zh: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
 
(No difference)

Latest revision as of 16:52, 10 May 2021

En Ar Bg De El Es Fa Fi Fr Hi Hu It Ja Kn Ko Ms Nl Pl Pt Ru Sq Th Tr Uk Zh

此页面用于描述新的信号和插槽语法。该功能现已随Qt 5一起发布。

注意:这是对旧的基于字符串的语法的补充,该语法仍然有效。

在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连接无关。