Public 函数与 public 槽

Public functions versus public slots

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

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

Slots can be used for receiving signals, but they are also normal member functions.

那么...是否有任何理由不将从 QObject 继承的 class 中的每个函数都声明为插槽,无论它是否需要一个?

在上面的link中,他们举了一个例子:

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() 函数定义为普通函数而不是插槽?如果他们真的让它成为一个插槽,会有任何负面结果吗?

对于某些函数,您需要 return 值。这在插槽中不会那么容易工作。在插槽中,您不能使用函数的 return 值或为它们提供参考参数。是的,你可以做到,但你有时间问题。 您是使用插槽还是普通成员函数取决于您的软件架构。

此外,事件循环中的插槽 运行。这是有意还是无意,取决于您的代码。

在过去,如果你想连接到信号,你别无选择,只能使用插槽。在 Qt 5 中不再是这种情况,在 Qt 5 中可以连接到常规成员函数甚至自由函数或 lambda。

声明一个插槽寄存器,该寄存器在该特定对象的元数据中发挥作用,使其可用于所有依赖于该元对象的 Qt 功能。除此之外,正如文档所述,它是一个常规成员函数。槽函数本身并没有什么特别之处,区别在于元对象中为其生成元数据。

这意味着声明插槽需要付出一些代价,尽管在编译时间和可执行文件大小方面都是很小的代价。我会说将所有 public 函数都作为插槽是多余的。仅当您确实需要插槽时才使用插槽会更有效,如果它不能与常规功能一起使用,请将其设为插槽。

此外,请注意,在几乎所有情况下,信号都是使用 return 类型 void 声明的。这与信号的典型使用案例有关——它们通常可以传递参数,但很少 return 传递任何东西。尽管可以通过连接到插槽的信号 return 值,但这种情况极少使用。因此,将 return 的函数声明为您打算连接的插槽没有多大意义,因为它 return 是一个值这一事实意味着它很可能不会在典型的 signal/slot 上下文中使用。这就是为什么 getter 在该示例中不是插槽的原因。 setter 作为插槽在 Qt 5 中是多余的,并且可能是可追溯到 Qt 4 的示例代码的产物。

最后,将插槽与常规 public 函数分开是说明意图的好方法,或者 class 的 "API" 如果您愿意的话。例如,我在扩展 QML 时主要使用插槽,因此我不必将每个函数显式标记为可调用 - 与上一段中提到的场景不同,此类插槽通常 return 东西,但它们实际上并不是在连接中使用。通过这种方式,我可以清楚地了解 class 提供的界面设计。

除了 ddriver 的答案,这是最佳/正确答案(+1),我还认为将所有成员函数定义为 public 槽是令人困惑的。他们定义函数的方式(private / public / slots 等...)对 class.

的感知使用有影响

我的意思是....您可能会争辩说所有函数都应该只是 public(或 public 插槽),然后这涵盖了所有情况。但是,这可能会让 class 的未来用户感到困惑。考虑到 int value() 是一个 public 插槽(这不是最好的例子)有人可以尝试将它用作一个插槽,但函数本身有一个 return 值,这对一个插槽。对于普通成员函数来说确实有意义,其中(作为普通函数调用)可以访问 return 值。

要遵守的一条规则是,始终将您的函数和变量保持在局部范围内并尽可能私有(默认情况下),并且仅将它们打开以供其他用途(public-ness、插槽、全局等...) 当你真正需要它们时。这使您的 class 界面更易于理解,并避免以后的用户混淆。

我确定这个经验法则有一个名称,因此您可以在一些编码技术网站上查找它,但我想不起来了:(

编辑

另一个小例子是自动完成...如果您的所有函数都是插槽,那么当您执行 connect(this, myClass::mySignal, &someOtherClass, SomeOtherClass::<auto complete options> ); 时,您的自动完成选项列表可能很长而且不清楚什么是什么。如果您只有几个特定的​​插槽成员,那么更容易知道选择哪个。

参考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();
}

正如我们在这里看到的,从 class.

外部调用函数 Hello 是成功的

另一个您可能不希望所有方法都成为插槽的用例是当您通过 Qt's WebKit Bridge 将对象公开给 JavaScript 时: 所有 public 插槽都可以从 JavaScript 调用,这可能会引发安全问题,具体取决于 JavaScript 是否可信。

因此,如果您想要一个无法从 JavaScript 调用的 public 方法,您可能不会将其声明为插槽。