Qt,绘制图像,这会导致内存问题吗?

时间:2016-03-28 15:47:59

标签: c++ qt

//Case 1:
QImage* tImg = new QImage("Some Image Here");
painter->drawImage(x, y, *tImg );
...
delete tImg;

//Case 2:
QImage* tImg = new QImage("Some Image Here");
{
    QImage aImg(*tImg);
    painter->drawImage(x, y, aImg );
}
...
delete tImg;

我正在尝试在工作线程中加载多个图像并在主线程中绘制它们。但是我不确定在绘制它们之后删除工作线程中的图像是否可以。

//Case 3:
...
//In worker thread
QImage* tImg = new QImage("Some Image Here");
mutex.lock();
matrix.insert(tImg); // matrix is a QList
mutex.unlock();
...
//In main thread
mutex.lock();
foreach(QImage* tImg, matrix)
{
    painter->drawImage(x, y, *tImg);
}
mutex.unlock();
...
//In worker thread
mutex.lock();
matrix.remove(tImg);
delete tImg;
mutex.unlock();

以上代码会导致问题吗?由于drawImage函数是"传递const引用"。这会导致任何内存问题吗?

如果delete tImg在另一个帖子上怎么办?如果我使用互斥锁来确保delete tImg仅在painter->drawImage(x, y, *tImg );之后调用

,那么会安全吗

1 个答案:

答案 0 :(得分:3)

不需要手动内存管理。你应该利用Qt为你做这件事。您可以通过信号槽连接传递图像,并使用该值将被自动复制的事实,并且任何对它的访问都将由Qt自动同步。

以下是如何做到这一点,非常简单,让编译器为您完成资源管理的所有艰苦工作:

// https://github.com/KubaO/stackoverflown/tree/master/questions/imageloader-36265788
#include <QtWidgets>
#include <QtConcurrent>

首先,让我们有一个带有信号的类,它是图像源。它有一个提供const引用类型图像的信号,因为如果需要跨越线程边界,任何复制都将由Qt自动完成。

class ImageSource : public QObject {
   Q_OBJECT
public:
   Q_SIGNAL void hasImage(const QImage & image);

方法生成图像,并发出信号。只要自动连接与hasImage信号一起使用,此方法就可以在任何线程中安全运行。在我们的例子中,我们总是从工作线程运行此方法,但我们也可以从主线程运行它 - 唯一的区别在于性能。

   /// This method is thread-safe (ignoring the UB of incrementing a shared int)
   void generate() {
      static auto counter = 0;
      QImage img(128, 128, QImage::Format_ARGB32);
      img.fill(Qt::white);
      QPainter p(&img);
      p.drawText(img.rect(), Qt::AlignCenter, QString::number(counter++));
      p.end();
      emit hasImage(img);
   }
};

我们需要该类的一个实例,以及显示图像的内容 - 比如QLabel

int main(int argc, char ** argv) {
   QApplication app{argc, argv};
   ImageSource source;
   QLabel label;
   label.show();

我们现在可以将hasImage连接到一个设置标签大小并在其上设置图像的仿函数。然后它立即在全局池的工作线程中再次运行图像生成器。这由QtConcurrent::run自动处理。

仿函数在主线程中运行:通过向connectconnect(--, --, context, --)提供上下文参数来确保这一点。仿函数在label.thread()中运行,正如我们希望的那样。

   QObject::connect(&source, &ImageSource::hasImage, &label, [&](const QImage & image){
      label.setFixedSize(image.size());
      label.setPixmap(QPixmap::fromImage(image));
      QtConcurrent::run(&source, &ImageSource::generate);
   });

由于连接是自动的,调用hasImage信号的效果会导致将槽调用发布到接收对象(label)线程的事件队列 - 这里是主线程的队列。事件循环获取插槽调用并执行它。因此,即使在工作线程中调用hasImage,图像也会自动复制并传递给主线程中的仿函数。

最后,我们生成第一个图像以启动该过程。

   QtConcurrent::run(&source, &ImageSource::generate); // generate the first image
   return app.exec();
}

最后需要#include来提供信号hasImage信号的实现,以及描述ImageSource类的元数据。它由moc生成。

#include "main.moc"

这是完整的代码,您可以将其粘贴到新项目中,编译并运行;或者从github链接下载完整的项目。

它显示了一个标签,可以在我的机器上以大约1000 / s的速率更新像素图。应用程序完全响应:您可以随意移动窗口,并随时退出。

有关线程图像加载器的另一个示例,请参阅this answer