一、对比
从Qt 5开始,QT提供了两种不同的方式来编写信号槽连接:基于字符串(SIGNAL/SLOT将信号/槽转成一个字符串)的连接语法和基于函数指针的连接语法。这两种语法各有利弊。下表总结了它们的区别:
字符串 | 函数指针 | |
类型检查时期: | 运行时 | 编译时 |
可以执行隐式类型转换 | √ | |
可以将信号连接到lambda表达式 | √ | |
可以将信号连接到参数多于信号的槽(使用默认参数) | √ | |
可以将C++函数连接到QML函数 | √ |
二、类型检查和隐式类型转换
基于字符串的connect在运行时才进行类型检查。这有三个局限性:
- 只有在程序开始运行后才能检测到连接错误。
- 不能在信号和槽之间进行参数的隐式转换。
- 无法解析typedef和名称空间。
2和3的存在,是因为字符串比较器不能访问C++类型信息,因此依赖于精确字符串匹配。
相反,编译器检查基于函数指针的连接。编译器在编译时捕获错误,启用兼容类型之间的隐式转换,并识别同一类型的不同名称。
三、与Lambda表达式建立连接
基于函数指针的连接语法可以将信号连接到C++ 11 lambda表达式,这些表达式实际上是内联槽。此功能在基于字符串的语法中不可用。
注意:基于函数指针的连接语法接受指向所有函数的指针,包括独立函数和常规成员函数。但是,为了可读性,信号应该只连接到槽、lambda表达式和其他信号。
四、将C++对象连接到QML对象
基于字符串的语法可以将C++对象连接到QML对象,但是基于函数指针的语法不能。这是因为QML类型在运行时被解析,因此它们不适用于C++编译器。
在下面的示例中,单击QML对象使C++对象打印消息,反之亦然。
QmlGui.qml:
Rectangle
{
width: 100; height: 100
signal qmlSignal(string sentMsg)
function qmlSlot(receivedMsg)
{
console.log("QML received: " + receivedMsg)
}
MouseArea
{
anchors.fill: parent
onClicked: qmlSignal("Hello from QML!")
}
}
class CppGui : public QWidget
{
Q_OBJECT
QPushButton *button;
signals:
void cppSignal(const QVariant& sentMsg) const;
public slots:
void cppSlot(const QString& receivedMsg) const
{
qDebug() << "C++ received:" << receivedMsg;
}
public:
CppGui(QWidget *parent = nullptr) : QWidget(parent)
{
button = new QPushButton("Click Me!", this);
connect(button, &QPushButton::clicked, [=]
{
emit cppSignal("Hello from C++!");
});
}
};
连接信号:
auto cppObj = new CppGui(this);
auto quickWidget = new QQuickWidget(QUrl("QmlGui.qml"), this);
auto qmlObj = quickWidget->rootObject();
// 连接QML信号到C++槽
connect(qmlObj, SIGNAL(qmlSignal(QString)),cppObj, SLOT(cppSlot(QString)));
// 连接C++信号到QML槽
connect(cppObj, SIGNAL(cppSignal(QVariant)),qmlObj, SLOT(qmlSlot(QVariant)));
注:QML中所有的JavaScript函数都采用var类型的参数,此类型参数映射到C++中的QVariant类型。
五、槽中的默认参数个数 < 信号中参数个数
基于字符串的连接语法为槽中的默认参数个数 < 信号中参数个数提供了一种解决方法:如果槽具有默认参数,则可以从信号中忽略这些参数。当发出的信号参数少于槽参数时,Qt使用默认参数值运行槽。
基于函数指针的连接不支持此功能。基于函数指针的连接要解除这种限制需使用lambda表达式连接。
六、重载信号槽
基于字符串的语法,可以显式指定参数类型。因此,重载信号或槽的期望实例是明确的。
基于函数指针的语法,必须强制转换重载信号或槽,以告诉编译器要使用哪个实例。
例:QLCDNumber 有几种重载槽:
- QLCDNumber::display(int)
- QLCDNumber::display(double)
- QLCDNumber::display(QString)
基于函数指针的重载几种形式(connect(obj1,地址1,obj2,地址2);):
auto slider = new QSlider(this);
auto lcd = new QLCDNumber(this);
connect(slider, &QSlider::valueChanged,lcd, static_cast<void (QLCDNumber::*)(int)>(&QLCDNumber::display));//函数的地址转成QLCDNumber类的函数指针可以指向的地址
void (QLCDNumber::*mySlot)(int) = &QLCDNumber::display;//指向QLCDNumber类的函数指针 = 成员函数的地址
connect(slider, &QSlider::valueChanged,lcd, mySlot);
//C++14以后才支持
connect(slider, &QSlider::valueChanged,lcd, qOverload<int>(&QLCDNumber::display));
1、auto qOverload(T functionPointer)
返回指向重载函数的指针。template参数是函数的参数类型列表。functionPointer是指向函数的指针(非成员函数也可以):
struct Foo {
void overloadedFunction();
void overloadedFunction(int, const QString &);
};
qOverload<>(&Foo::overloadedFunction) //返回指向void overloadedFunction()的地址
qOverload<int, const QString &>(&Foo::overloadedFunction)
2、auto qConstOverload(T memberFunctionPointer)
返回指向const成员函数的指针(后面带const的):
3、auto qNonConstOverload(T memberFunctionPointer)
返回指向非const成员函数的指针。
struct Foo {
void overloadedFunction(int, const QString &); //A
void overloadedFunction(int, const QString &) const; //B
};
qConstOverload<int, const QString &>(&Foo::overloadedFunction) //对应B
qNonConstOverload<int, const QString &>(&Foo::overloadedFunction) //对应A