【问题标题】:Using Qt signals and slots vs calling a method directly使用 Qt 信号和槽与直接调用方法
【发布时间】:2014-03-20 15:44:40
【问题描述】:

假设我有一个带有滑块的主窗口和一个在该窗口内的小部件,该窗口带有一个名为setValue(int) 的方法。每次滑块的值发生变化时,我都想调用这个方法。

以下两种实现方式有什么实际区别:

1

void MainWindow::on_slider_valueChanged(int value)
{
    ui->widget->setValue(value);
}

2

// somewhere in constructor
connect(ui->slider, SIGNAL(valueChanged(int)), ui->widget, SLOT(setValue(int)));

对我来说,第一种方法看起来更好,因为它可能避免一些与信号和插槽机制相关的开销,并且如果需要,我还可以在将 value 发送到 widget 之前对其进行处理。

有没有第二种方案更好的场景?

【问题讨论】:

  • 你是怎么接到MainWindow::on_slider_valueChanged的电话的?这才是重点。信号提供事件及其处理程序的抽象。如果您没有瓶颈,则不要关心性能(至少如果它影响您的设计),尤其是 GUI 代码。 GUI 代码绝对与性能无关。
  • @Paranaix: MainWindow::on_slider_valueChanged 被调用是因为QMetaObject::connectSlotsByName() 自动生成此连接。我关心的不是性能,而是设计。我试图找出为什么一个比另一个更好,如果它有任何区别的话。
  • 信号和槽方法的一个明显优势是信号/槽连接在其任一端点被删除时自动且安全地断开。这意味着没有由于访问悬空指针而导致崩溃的风险(例如,如果您在某处执行了“删除 ui->widget”,您对 on_slider_valueChanged() 的下一次调用将崩溃,但示例 2 将继续运行而不会出现错误)跨度>
  • 第二种方法肯定更好。在第一个中,您再引入一个实体 (on_slider_valueChanged) 只是为了调用一些体面的代码行。此外,那些将阅读您的代码的人现在必须滚动浏览所有微方法才能找出连接方案。
  • 在我看来,情况正好相反。第二个更直接 - 它直接将信号与插槽连接。第一个是间接的——你将信号连接到你的自定义处理程序,然后调用槽。第一个并没有“避免一些与信号和槽相关的开销”,相反,它增加了一些开销。

标签: c++ qt user-interface class-design signals-slots


【解决方案1】:

在您的示例中,使用信号而不是直接调用的主要区别是允许多个侦听器。

如果你直接调用你的小部件setValue(),那么只有那个小部件会收到C++信号。

如果您使用 Qt 信号,现在任何其他对象都可以连接以接收事件发生时。

如果您没有预见到任何其他对象想要通过信号接收值,我不会为此烦恼。直接调用肯定要快得多(在 3 到 6 个 CPU 指令之间,而不是处理字符串来查找接收器!),但正如 Paranaix 所提到的,在 GUI 中它可能不是什么大问题(尽管在这种情况下它可能成为如果您在移动滑块时发送所有这些信号,那么旧计算机上会出现问题。)

【讨论】:

  • AFAIK 处理字符串以查找接收者只发生在 connect() 和 disconnect() 调用期间,而不是在信号发射期间。
  • @JeremyFriesner 在带有新型信号/插槽连接的 Qt5 上,根本不需要处理字符串 :)
  • 没有直接调用。两种情况都使用信号槽连接。
  • 有一个直接调用,尽管这两种情况都使用信号槽机制。
  • 你没有提到处理多线程的机制的巨大优势,所以我是-1。分为GUI线程和工作线程是信号槽的主要用途之一。
【解决方案2】:

Signals & Slots 是一种不同的编码风格。您可以使用对传统 c++ 有用且简洁的信号来做一些事情。例如,您可以从 const 函数发出 const 信号并将它们连接到非 const 插槽(在 c++ 中,您不能从 const 函数进行非 const 调用)。我从不喜欢使用可变对象,所以信号为我提供了一个干净的工作。

【讨论】:

    【解决方案3】:

    这两种方法都使用信号槽连接。在第一种情况下,connect 调用是由从setupUi 调用的QMetaObject::connectSlotsByName() 进行的。在第二种情况下,您自己显式调用connect

    此外,在使用 C++11 时,Qt5 中不需要第一种方法。您可以修改 lambda 中的值:

    QObject::connect(ui->slider, &QAbstractSlider::valueChanged,
                     [this](int val){ ui->widget->setValue(val*2); });
    

    为了防止删除ui->widget,您应该使用QPointer

    class MyWindow : public QMainWindow {
      QPointer<QAbstractSlider> m_widget;
      ...
    public:
      MyWindow(QWidget * parent = 0) : QMainWindow(parent) {
        ...
        setupUi(this);
        m_widget = ui->widget;
        QObject::connect(ui->slider, &QAbstractSlider::valueChanged, 
                        [this](int val)
        {
          if (!m_widget.isNull()) m_widget->setValue(val*2); 
        });
    

    信号槽连接的开销为quantified in this answer

    【讨论】:

      【解决方案4】:

      信号/插槽优势:

      • 多个插槽可以连接到单个信号,您无需为此分配和释放内存
      • 你可以用这个处理多线程

      信号/插槽缺点:

      • 比直接调用慢一点
      • 如果插槽是虚拟的,速度会明显变慢
      • QObject 是相当重的东西,所以你通常会尽量避免构建数十亿个对象

      更多详情可here

      【讨论】:

      • + 用于提及多线程。插槽可能在与信号不同的线程中被调用,这是这种机制的巨大(甚至可能是最好的)优势。
      【解决方案5】:

      我更喜欢第二种方法,因为我碰巧在删除 UI 元素时忘记删除“auto-connect-slots”,导致死代码。 AFAIK 它与“幕后”相同(查看自动生成的 qt 文件)。

      当您想修改值时,我更喜欢以下方法:

      connect(ui->slider, SIGNAL(valueChanged(int)), this, SLOT(myOwnSlot(int)));
      
      void MainWindow::myOwnSlot(int value) {
          /** do stuff */
          ui->widget->setValue(value);
      }
      

      问候

      【讨论】:

        猜你喜欢
        • 2016-10-21
        • 1970-01-01
        • 1970-01-01
        • 2012-10-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-27
        • 2023-03-05
        相关资源
        最近更新 更多