在Qt信号中发射QVector参考会导致复制
Emitting QVector reference in Qt signal results in copy
我正在尝试通过构建一个应用程序来与线扫描相机交谈。 最终,我想每 100 毫秒传递一个 384x128 unsigned short
值的"块"(即数组(,从QThread
(数据采集(传递到QRunnable
(数据处理(。 这意味着QRunnable
在下一个区块到达之前将有 100 毫秒的时间来处理数据。
我仍然不确定移动数据的正确方法。 现在,我正在使用QVector。 在Qt4中,我理解隐式共享意味着QVector如果在信号中发出,则在写入对象之前不会被复制。 但是,在我制作的一个小测试应用程序中,我不确定我是否确切理解这意味着什么。 以下是下面提供的 MWE 的输出...
Acquire thread: init.
Acquire thread: block acquired: 0x106485e78 Content: {1, 2, 3, 4}
GUI thread: received signal: 0x7fff5fbfda98 Content: {1, 2, 3, 4}
Acquire thread: block acquired: 0x106485e78 Content: {1, 2, 3, 4}
GUI thread: received signal: 0x7fff5fbfda98 Content: {1, 2, 3, 4}
Acquire thread: block acquired: 0x106485e78 Content: {1, 2, 3, 4}
GUI thread: received signal: 0x7fff5fbfda98 Content: {1, 2, 3, 4}
我正在使用具有四个值的"虚拟"QVector 并在线程运行时跟踪矢量的地址。 数据自始至终都是正确的,但似乎制作了副本。 我没有在应用程序的任何时候更改数据...只是显示。 我尝试在整个过程中使用const QVector<unsigned short>
,使用引用的各种迭代等。 地址总是更改。 由于性能在这里很重要,因此我担心在 384*128 值时复制 QVector。
此外,在另一个 SO 问题中,我正在努力弄清楚如何让 QRunnable 接收数据(此示例中全部省略(。 但是,这里需要注意这一点很重要,因为我的想法是让 QRunnable 在对 QThread 中存在的image_buffer
的引用上运行。 我只是不知道该怎么做。
具体问题:
-- 为什么看起来是复制品? 这是因为多线程吗?
-- 我是否必须在整个过程中显式使用引用(即image_buffer&(,从而完全删除QVector? 或者,我应该担心副本吗?
-- 根据我的情况将数据从QThread传递到QRunnable的正确方法是什么?
下面的 MWE 演示了不同的地址。 免责声明:我边走边学C++。 没有正式的培训,只有几本好书摆在我面前。
主.cpp
#include <QApplication>
#include <QMetaType>
#include <QVector>
#include "appwidget.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
AppWidget gui;
gui.show();
qRegisterMetaType<QVector<unsigned short> >("QVector<unsigned short>");
return app.exec();
}
** appwidget.h **
#ifndef APPWIDGET_H
#define APPWIDGET_H
#include <QWidget>
#include <QVector>
#include "acquire.h"
class AppWidget : public QWidget
{ Q_OBJECT
public:
AppWidget(QWidget *parent = 0);
protected:
Acquire thread;
public slots:
void processBlock(QVector<unsigned short>);
};
#endif
应用小部件.cpp
#include <QtGui>
#include <iostream>
#include "appwidget.h"
AppWidget::AppWidget(QWidget *parent)
: QWidget(parent)
{
thread.liftoff();
connect(&thread, SIGNAL(blockAcquired(QVector<unsigned short>)), this, SLOT(processBlock(QVector<unsigned short>)));
setWindowTitle(tr("TestApp"));
resize(550, 400);
}
void AppWidget::processBlock(QVector<unsigned short> display_buffer)
{
std::cout << "GUI thread: received signal: "
<< &display_buffer
<< " Content: {"
<< display_buffer.at(0) << ", "
<< display_buffer.at(1) << ", "
<< display_buffer.at(2) << ", "
<< display_buffer.at(3)
<< "}" << std::endl;
}
获取.h
#ifndef ACQUIRE_H
#define ACQUIRE_H
#include <QVector>
#include <QThread>
class Acquire : public QThread {
Q_OBJECT
public:
Acquire(QObject *parent = 0);
~Acquire();
QVector<unsigned short> display_buffer;
void liftoff();
signals:
void blockAcquired(QVector<unsigned short>);
protected:
void run();
private:
};
#endif
收购.cpp
#include <iostream>
#include <time.h>
#include <stdlib.h>
#include "acquire.h"
Acquire::Acquire(QObject *parent)
: QThread(parent)
{
}
Acquire::~Acquire()
{
std::cout << "Acquire thread: dying." << std::endl;
wait();
}
void Acquire::liftoff()
{
std::cout << "Acquire thread: init." << std::endl;
start();
}
void Acquire::run()
{
QVector<unsigned short> display_buffer(2 * 2);
forever {
/*
display_buffer will ultimate be a memcpy of image_buffer
.. image_buffer updated continuously, 1 line every 800000ns x 128 lines
*/
display_buffer[0] = 1;
display_buffer[1] = 2;
display_buffer[2] = 3;
display_buffer[3] = 4;
nanosleep((struct timespec[]){{0, 800000*128}}, NULL);
std::cout << "Acquire thread: block acquired: "
<< &display_buffer
<< " Content: {"
<< display_buffer.at(0) << ", "
<< display_buffer.at(1) << ", "
<< display_buffer.at(2) << ", "
<< display_buffer.at(3)
<< "}" << std::endl;
emit blockAcquired(display_buffer);
}
}
在这种情况下,将创建副本,因为您是通过值传递的,也是因为跨线程边界的信号是排队的。不过这很好,因为隐式共享意味着它们是浅拷贝。如果原件和副本都仅用于阅读,则复制几乎没有开销。
不幸的是,在您的程序中实际上并非如此。您的永久循环将在信号发射后循环回时修改矢量。在这个例子中,它实际上不会改变向量中的任何内容,因为你总是只分配 1,2,3,4,但调用 non-const 运算符 [] 足以触发深层复制。
我的结论是:你可以是同步的,并在读取器和写入器之间共享相同的缓冲区,或者你可以是异步的,并将写入缓冲区的副本提供给读取器。您不能是异步的,并且在读取器和编写器之间共享相同的缓冲区。
您处理此问题的方式对于异步处理来说似乎很好。根据数据生成和数据处理的特征,您可能会(也可能不会(找到更好的同步解决方案。使异步代码同步的最简单方法是将连接类型提供为"连接时Qt::BlockingQueuedConnection
"。
要回答第二个问题,您可以创建一个多重继承,QObject
到您的QRunnable
(只需确保 QObject 始终是列表中的第一个(。然后,您可以使用信号/时隙机制传递数据,就像在测试示例中一样。
class DataProcessor : public QObject, public QRunnable
{ Q_OBJECT
public:
public slots:
void processBlock(QVector<unsigned short>);
};
实际上,您也可以将此结构用于Acquire
类,因为您的目标是在不同的线程中运行代码,而不是向线程本身添加额外的功能。
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 在C++程序中输入的文本文件将不起作用,除非文本被复制和粘贴
- 使用strcpy将char数组的元素复制到另一个数组
- 是否可以初始化不可复制类型的成员变量(或基类)
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- 通用参考 l 值不复制对象
- C 标准:通过复制返回以初始化无RVO的参考:是否有任何副本
- 如果值来自成员变量,则复制初始化和参考初始化之间的C 差异
- 如何使对象通过RVALUE参考通过而没有复制
- 复制C DLL以构建任何C#项目的输出,以扩展为参考
- 为什么合成的复制分配运算符被定义为如果类有参考成员,则将其定义为删除
- 参考的复制构造可以使用私有成员变量
- 如果lvalue,请参考,如果rvalue进行复制,即使rvalue持久
- Qt信号和插槽:是复制的参考参数
- C 复制分配运算符,用于参考对象变量
- 直接初始化和复制参考初始化
- c 参考class对象的返回 - 为什么未调用复制构造函数
- C 显式通用参考构造器不会隐藏复制构造函数
- 在Qt信号中发射QVector参考会导致复制
- 正在变量模板中复制的参考参数