QImage: Как правильно уничтожить указатель на данные пикселей?

В моем основном классе, который управляет окном, у меня есть эта функция, где pixmapItem — это QGraphicsPixmapItem*, определенный в заголовке класса:

void updateDisplay() {
    uchar *data = new ...; // array of pixel data
    ...
    QImage image = QImage(data, width, height,
                          width, QImage::Format_Indexed8);

    pixmapItem->setPixmap(QPixmap::fromImage(image));
}

Мой вопрос: как я могу уничтожить data, когда он больше не нужен? «Больше не нужно» означает, что функция выше или другая функция в моем классе устанавливает растровое изображение на другое изображение.

Я видел, что в QImage есть функции очистки, которые могут помочь, но в документации не совсем понятно, как их использовать и как передавать такие параметры, как указатель данных удаляемого изображения.


person mimo    schedule 05.12.2016    source источник
comment
Обратите внимание, что обычно, если это возможно, лучше позволить QImage выделять память для пикселей самостоятельно, чтобы вы могли быть уверены, что ее размер и выравнивание правильные, и она принадлежит/автоматически уничтожается экземпляром QImage; после создания QImage вы можете получить указатель на пиксельные данные, используя метод bits.   -  person Matteo Italia    schedule 05.12.2016


Ответы (5)


Вы можете удалить данные, когда QImage будет уничтожен; это, как показано выше @JeremyFriesner, можно просто сделать, определив экземпляр QImage во внутренней области и выделив/освободив данные вне его.

Однако зачем возиться со всей этой работой? Этот конструктор QImage предназначен для сложных случаев использования, когда у вас уже есть пиксельные данные из какого-то другого источника или вам нужно сохранить их на «странное» время жизни.

Вместо этого ваш случай намного проще, данные пикселей имеют точно такое же время жизни, что и QImage, поэтому лучше позволить ему выделять память для пикселей самостоятельно; таким образом вы можете быть уверены, что он имеет правильный размер и выровнен, и что он принадлежит/автоматически уничтожается экземпляром QImage; после создания объекта QImage вы можете получить указатель на данные пикселя, используя метод bits, и делать с ним все, что захотите.

Таким образом, ваш код просто станет:

void updateDisplay() {
    QImage image = QImage(width, height,
                          width, QImage::Format_Indexed8);
    uchar *data = image.bits();
    ... 

    pixmapItem->setPixmap(QPixmap::fromImage(image));
}

проще, безопаснее и с нулевым риском утечки памяти.

person Matteo Italia    schedule 05.12.2016

Из документации Qt

Создает изображение с заданной шириной, высотой и форматом, которое использует существующий буфер памяти data. Ширина и высота должны быть указаны в пикселях, данные должны быть выровнены по 32 битам, и каждая строка развертки данных в изображении также должна быть выровнена по 32 битам.

Буфер должен оставаться действительным в течение всего срока службы QImage. Образ не удаляет буфер при уничтожении.

Вам нужно реализовать свой собственный способ удаления этого буфера

person Swift - Friday Pie    schedule 05.12.2016
comment
Да, я знаю, что образ не уничтожает data. Но я не знаю, как добиться этого в конкретном примере, который я привел! - person mimo; 05.12.2016

Таким образом, вам нужно удалить data самостоятельно, но, конечно, хитрость заключается в том, чтобы не удалять его слишком рано — в частности, вы не хотите удалять его, пока объект QImage все еще может его использовать. Самый простой способ убедиться, что в вашем случае это удалить только после уничтожения объекта QImage:

void updateDisplay() {
   uchar *data = new ...; // array of pixel data
   ...
   {
      QImage image = QImage(data, width, height,
                      width, QImage::Format_Indexed8);

      pixmapItem->setPixmap(QPixmap::fromImage(image));
   }
   delete [] data;
}

(обратите внимание на использование фигурных скобок для создания внутренней области видимости и тем самым убедитесь, что объект QImage извлекается из стека и уничтожается до выполнения строки delete [] data!)

Конечно, более простым и безопасным подходом было бы вообще избежать вопроса о том, когда удалять, никогда не выделяя массив вручную. Вместо этого позвольте объекту QImage выделить свой собственный массив данных и вместо этого просто напишите в него:

QImage image(width, height, QImage::Format_Indexed8);
char * data = image.bits();
// write into (data) here if you want to, but don't delete [] data, ever!  
// instead, the QImage destructor will do any necessary deletes for you
person Jeremy Friesner    schedule 05.12.2016

Вы можете создать подкласс QImage со своим собственным деструктором, который выполняет очистку.

class NativeBufferQImage : public QImage {
    uchar *data;

    // other stuff

public:
    ~NativeBufferQImage() {
        delete[] this->data; 
    }
};

Также обратите внимание, что поскольку QImage является производным от QPaintDevice, а QPaintDevice имеет виртуальный деструктор, поэтому, если кто-то delete ваш NativeBufferQImage указателем на его базовый класс (т.е. QImage), ваш деструктор тоже будет вызван.

person frogatto    schedule 05.12.2016
comment
QImage не происходит от QObject. Он наследуется от QPaintDevice. Но виртуальный деструктор все еще там. - person thuga; 05.12.2016
comment
@thuga Ооо... спасибо, что заметили это. отредактирует мой ответ. - person frogatto; 05.12.2016
comment
это устраняет преимущество использования изображений с общими данными или использования данных с более длительным сроком службы, чем объект QImage. Подкласс QImage для использования умного указателя? Каков конкретный вариант использования, когда вам нужно использовать независимый буфер данных для построения QImage? - person Swift - Friday Pie; 05.12.2016

Лучший способ сделать это — не делать этого:

void updateDisplay() {
    QImage image(width, height, QImage::Format_Indexed8);
    auto const data = image.bits();
    // you can modify the data here
    pixmapItem->setPixmap(QPixmap::fromImage(image));
}
person Kuba hasn't forgotten Monica    schedule 05.12.2016
comment
это не всегда лучший метод. Внутреннее хранилище Qt представляет собой фрагментированный беспорядок. Создание изображения из буфера данных позволяет использовать пул памяти. Qt Ofc, никто не должен просто болтаться с неуправляемыми указателями XD - person Swift - Friday Pie; 05.12.2016
comment
Разрозненный беспорядок? Qt использует распределитель памяти, предоставляемый средой выполнения C++. На любой разумной платформе это должен быть приличный нефрагментирующий распределитель. И задавший вопрос не указал особых потребностей в этой области. - person Kuba hasn't forgotten Monica; 05.12.2016
comment
@ Куба Обер, этот довольно наивный взгляд на это. Нет, среда выполнения C++ не гарантирует, что фрагментация памяти не произойдет, также все объекты, когда-то выделенные, являются статическими, поэтому дефрагментация невозможна. Как разработчик встраиваемого программного обеспечения, где не допускается любой сценарий фрагментации, я знаю, что Qt не соответствует даже приличному уровню управления фрагментацией, потому что большинство объектов Qt неподвижны. Это одна из причин, по которой у них есть собственная реализация контейнеров, например QList. Изображения могут содержать большой объем данных, иногда гигабайты, что может быстро привести к ухудшению места в куче. - person Swift - Friday Pie; 07.12.2016
comment
Что касается стандартов: конечно, среда выполнения не должна гарантировать это. Но может, и в большинстве случаев так и есть. Например. Распределитель памяти в msvcrt не является фрагментирующим: после выделения блок, используемый для этого выделения, не будет разбиваться на более мелкие куски. - person Kuba hasn't forgotten Monica; 07.12.2016
comment
Это на самом деле плохо в сценарии, о котором я думаю, я видел проект, который оперирует несколькими тысячами изображений (текстур), обрабатывает их для рендеринга, скачивания, загрузки, генерации. Они не используют Qt, но именно то поведение, которое вы описали, привело к тому, что программе не хватило памяти в течение часа. Выделенный блок не фрагментируется, после освобождения остаются дыры, тогда вам нужно выделить меньший кусок или большой кусок ... и у вас нет памяти. Сегодня он может работать несколько дней, потому что использует пул памяти для хранения всех данных текстуры. О, если вам интересно, ищите программу просмотра Second Life. - person Swift - Friday Pie; 07.12.2016