公共函数与公共槽的区别

13

在我一年的Qt编程中,我学到了很多关于信号和槽的知识。但是还不够...

http://doc.qt.io/qt-5/signalsandslots.html

槽函数可以用于接收信号,但它们也是普通成员函数。

那么...有没有理由不将从QObject继承的每个函数都声明为槽函数,无论它是否需要成为槽函数?

在上面的链接中,他们给出了一个例子:

A small QObject-based class might read:

#include <QObject>

class Counter : public QObject
{
    Q_OBJECT

public:
    Counter() { m_value = 0; }

    int value() const { return m_value; }

public slots:
    void setValue(int value);

signals:
    void valueChanged(int newValue);

private:
    int m_value;
};
为什么将value()函数定义为普通函数而不是槽函数?如果将其定义为槽函数,是否会产生任何负面影响?
5个回答

19

在旧版本的Qt中,如果想要连接到信号,则只能使用槽函数。但在Qt 5中,连接可以与普通成员函数、自由函数或lambda表达式建立。

声明一个槽将该函数注册到特定对象的元数据中,使其对依赖于元对象的Qt所有功能都可用。除此之外,就像文档所述,它是一个普通的成员函数。槽函数本身没有什么特别之处,其区别在于元对象为其生成的元数据。

这意味着,虽然编译时间和可执行文件大小都会受到一些小小的影响,但声明槽的成本并不高昂。我认为将所有公共函数都作为槽可能有些过度设计。只有在真正需要槽时才使用槽更有效率,如果只能用槽函数而不能用普通函数,则将其作为槽。

此外,请注意,在几乎所有情况下,信号的返回类型均为void。这与信号的典型用法有关——它们通常可以传递参数,但很少返回任何内容。尽管可以通过连接到槽来返回一个值,但这种情况非常罕见。因此,将返回某些东西的函数声明为槽并不太合理,因为它返回一个值的事实意味着它最有可能不在典型的信号/槽上下文中使用。这就是为什么在该示例中getter不是槽的原因。Qt 5中,将setter作为槽是多余的,很可能是由于此示例代码追溯到Qt 4时代。

最后,将槽函数与普通公共函数分开是展示意图或该类“API”的好方法。例如,我大多数时候在扩展QML时使用槽,以便不必明确标记每个功能为可调用 - 与上面所述情况不同,这些槽通常返回一些内容,但它们并未在连接中使用。这样我可以清晰地了解该类提供的接口设计。


2

参考code_fodder的回答中的封装,需要注意的是,实际上不存在私有插槽。

比如:

class MyClass : public QObject
{
    Q_OBJECT
public:
    MyClass()
        :QObject(NULL) {}

private slots:
    void Hello() { qDebug("Hello World\n"); }
};

#include "main.moc"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MyClass* cls = new MyClass;
    QMetaObject::invokeMethod(cls, "Hello");

    return a.exec();
}

我们可以看到,在类外部调用Hello函数是成功的。


它对连接也有效吗?顺便说一句,调用私有函数的可能性完全取决于Qt,没有访问检查。尽管如此,私有插槽确实存在,即使元系统不予以尊重 :) - dtech
是的,如果您使用旧的连接方法(Qt 5之前),可以使用SIGNAL和SLOT宏进行连接。例如,使用QTimer connect(pTimer, SIGNAL(timeout()), cls, SLOT(Hello()) ); - TheDarkKnight
1
@TheDarkKnight 这是一个非常有趣的观察(+1 分享)。当在同一类中设置内部连接时,我总是使用私有槽,否则使用公共槽...但我从未想过检查(或注意到)Qt不会禁止私有槽的可见性!-尽管如此,这不会改变我定义它们的方式,因为即使Qt宽容(或只是在这里出错),我也不喜欢这样做 :) - code_fodder
1
我已经注意到这种(奇怪的)行为 - 我在文档中找到了这些信息 - 这就是为什么我特别询问“公共”函数和插槽的原因。还是谢谢你添加这些信息。 - Thalia
@code_fodder 像你一样,我也声明私有槽。至少这样,界面会显示应该被保持私有的设计内容,即使它仍然可以公开访问。 - TheDarkKnight
@Thalia,我认为这背后的原因是使用宏决定了运行时的连接,所以此时的封装是无关紧要的。相比之下,Qt 5的连接提供编译验证,并在目标槽是私有时引发错误。 - TheDarkKnight

2
除了ddriver的答案(那是最好/正确的答案,+1),我还要说一下,将所有成员函数定义为public slots是令人困惑的。你定义函数的方式(private/public/slots等)对类的使用有影响。
我的意思是……你可以认为所有函数都应该是public(或public slots),这样就覆盖了所有情况。然而,这可能会让未来使用你的类的用户感到困惑。考虑到int value()是一个公共槽(它不是最好的例子),有人可能会尝试将其用作槽,但是该函数本身具有返回值,这对于槽来说并没有真正意义。对于普通成员函数来说,这是有意义的,因为(作为普通函数调用)可以访问返回值。
一个经验法则是,尽可能将函数和变量保持为本地作用域和私有作用域(默认情况下),只有在真正需要时才将它们开放给其他用途(公共性、槽、全局等)。这使得你的类接口更容易理解,避免了后续用户的混淆。
我相信这个经验法则有一个名称,你可以在某些编码技术网站上查找,但我无法回忆起它的名字:(
另一个小例子是自动完成……如果你的所有函数都是槽,那么当你做连接(this,myClass :: mySignal,&someOtherClass,SomeOtherClass :: <自动完成选项>)时,你的自动完成选项列表可能会很长,不清楚哪个是哪个。如果只有一些特定的成员是槽,那么就更容易知道应该选择哪一个了。

1
你要找的术语是封装。 - thuga
@thuga - 是的,封装肯定是主题...但我相信有一些保持最小范围的“规则名称”...也许只是“封装规则”?谢谢:) - code_fodder
我想补充一点,就是公共槽函数是 Q_INVOKABLE 的,你可能不希望所有公共方法都可以从 QML 上下文中访问。 - MrEricSir

0

另一个使用情况是,当您通过Qt's WebKit Bridge将对象公开给JavaScript时,可能不希望所有方法都成为槽:

所有公共槽都可以从JavaScript调用,这可能会打开安全问题,具体取决于JavaScript是否可信。

因此,如果您想要一个公共方法,但不希望它从JavaScript中调用,则不应将其声明为槽。


我的问题主要原因是因为我必须将对象暴露给JavaScript - 使用我应用程序的人非常乐意请求几乎每个函数都可以作为插槽。我正在尝试限制这一点......但我需要一个理由。对于返回值的函数,通过将它们设置为 Q_PROPERTY,可以使它们从JavaScript中调用。 - Thalia

-1
对于某些函数,您需要返回值。在插槽中这不会那么容易实现。在插槽中,您不能使用函数的返回值或将引用参数传递给它们。虽然可以做到,但会有时间问题。 无论是使用插槽还是普通成员函数取决于您的软件架构。
此外,插槽在事件循环中运行。这取决于您的代码是否有意为之。

1
这个回答一点意义都没有,甚至似乎没有回答问题。 - MrEricSir
你能在槽中使用返回值吗?或者以正确的方式获取引用参数的值吗? - SuperFliege
1
@SuperFliege - 插槽只是一个函数,它的工作方式与函数完全相同。多线程和对象生命周期是完全不同的问题,它们对常规函数的影响与它们对插槽的影响一样大。 - dtech

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接