不建议通过Qt信号发出大量数据

5 c++ qt signals qt5

为什么不建议在Qt中通过信号发出大量数据?

为什么我们可以选择通过它们发送我们自己的类型?

Rei*_*ica 9

为什么不建议在Qt中通过信号发出大量数据?

没有这样的建议.请参阅此问题以供讨论.

数据与对象

首先,当我们通过信号参数传递数据时,我们通过值或引用传递对象实例.

然而,必须区分数据对象.A QString可能包含大量数据,但并不意味着它会在复制字符串对象时复制数据.

// one million worth of 'a's, about 2 megabytes worth of data
const QString large1(1000*1000, QLatin1Char('a'));
Run Code Online (Sandbox Code Playgroud)

large1是一种QString类型的对象.碰巧的是,QString实现使用隐式数据共享和复制对象 不复制数据.因此,复制字符串很便宜,尽管它比复制指针值更昂贵.

现在让我们考虑另一种字符串类型:

// one million worth of 'a's
const std::string large2(1000*1000, 'a');
Run Code Online (Sandbox Code Playgroud)

large2是一种std::string类型的对象.大多数的实现不使用隐式数据共享,并复制对象 将复制数据.

复制什么时候发生?

信号槽系统中有三种情况会强制复制一个对象:

  1. 在信号发射时,当信号的参数类型是值类型时.例如:

    Q_SIGNAL void mySignal(std::string); // copies or moves the object and data
    Q_SIGNAL void mySignal(const std::string &); // no copies here
    Q_SIGNAL void mySignal(QString); // copies or moves the object, but not the data
    Q_SIGNAL void mySignal(const QString &); // no copies here
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在调用插槽时,插槽的参数类型是值类型.实例与上述相同.

  3. 每次使用排队连接来调用插槽.

    当您选择排队的连接类型,或者连接是自动的(默认情况下),并且接收器对象在发出信号时存在于另一个线程中时,会发生这种情况.

例子

假设我们有以下课程:

class C : public QObject {
public:
  Q_SIGNAL void signal1(const QString &); // correct
  Q_SIGNAL void signal2(QString); // don't do that
  Q_SIGNAL void signal3(const std::string &); // correct
  Q_SIGNAL void signal4(std::string); // really don't do that

  Q_SLOT void slot1(const QString &); // correct
  Q_SLOT void slot2(QString); // only do that if you need a value to modify
  Q_SLOT void slot3(const std::string &); // correct
  Q_SLOT void slot4(std::string); // only do that if you need a value to modify
};
Run Code Online (Sandbox Code Playgroud)

我们现在可以尝试多种信号和插槽组合.假设在每种情况下信号都发出N一次,并且它连接到M插槽:

对象副本数,直接连接数

      signal1 signal2 signal3 signal4
slot1    0       N       -       -
slot2   N*M    N*(M+1)   -       -
slot3    -      -        0       N
slot4    -      -       N*M   N*(M+1)
Run Code Online (Sandbox Code Playgroud)

数据副本数,直接连接

      signal1 signal2 signal3 signal4
slot1    0      0        -       -
slot2    0      0        -       -
slot3    -      -        0       N
slot4    -      -       N*M   N*(M+1)
Run Code Online (Sandbox Code Playgroud)

对象,排队连接的副本数

      signal1 signal2 signal3 signal4
slot1    N*M  N*(M+1)    -       -
slot2   2*N*M 2*N*(M+1)  -       -
slot3    -      -       N*M    N*(M+1)
slot4    -      -      2*N*M  2*N*(M+1)
Run Code Online (Sandbox Code Playgroud)

数据副本数,排队连接数

      signal1 signal2 signal3 signal4
slot1    0      0        -       -
slot2    0      0        -       -
slot3    -      -       N*M    N*(M+1)
slot4    -      -      2*N*M  2*N*(M+1)
Run Code Online (Sandbox Code Playgroud)

有关测试用例,请参阅此答案.


dte*_*ech 3

为什么不建议在 Qt 中通过信号发出大量数据?

因为它通常涉及复制数据,尽管您也可以通过引用传递。并且复制数据需要时间。如果选择通过引用传递,则必须牢记对象的生命周期,否则最终会出现悬空引用和崩溃。所以从这方面来说按值传递是比较安全的。

那么为什么我们可以选择通过它们发送我们自己的类型呢?

因为否则你将如何发送数据?如果没有办法做任何计算成本很高的事情,你就无法用计算机做很多事情,不是吗?

Qt对它的大多数容器类(包括容器类)使用隐式共享QImage。这意味着当您按值传递时,不会复制实际的图像数据,只会进行浅复制,这要便宜得多。