LINUX.ORG.RU

[Qt Undo Framework] Борьба с утечками памяти в QUndoCommand'ах


0

1

Итак. У меня есть команды для создания и для удаления некоторого объекта из модели.

class CreateItemCommand : public QUndoCommand
{
public:
    CreateItemCommand(ItemModel *model, QUndoCommand *parent = 0):
      QUndoCommand(parent),
      m_model(model),
      m_item(new Item) {
    } 

    void redo() {
        m_model->addItem(m_item);
    }
    void undo() {
        m_model->removeItem(m_item);
    }
private:
    ItemModel *const m_model;
    Item *const m_item;
};
class RemoveItemCommand : public QUndoCommand
{
public:
    RemoveItemCommand(ItemModel *model, Item *item, QUndoCommand *parent = 0):
      QUndoCommand(parent),
      m_model(model),
      m_item(item) {
    }

    void redo() {
        m_model->removeItem(m_item);
    }
    void undo() {
        m_model->addItem(m_item);
    }
private:
    ItemModel *const m_model;
    Item *const m_item;
};

Всё бы хорошо, но такой простой подход чреват утечками памяти. Если так получилось, что на итем не ссылаются больше никакие команды (то есть они уничтожены тем или иным способом) и он не находится в ItemModel, то он занимает лишнюю память.

Qt Undo Framework устроен так, что команды в могут удаляться в одном из следующих случаев:

  1. делается QUndoStack::clear();
  2. при отменённой команде в QUndoStack добавляется некоторая другая команда;
  3. в QUndoStack установлен лимит количества команд (undoLimit) и мы вышли за его пределы.

Таким образом, команды могут удаляться находясь как в отменённом, так и в применённом состоянии. И контролировать память при этом несколько затруднительно.

В случае собственной модели решение проблемы простое: нигде не храним указатели на Item напрямую. Везде используем QSharedPointer<Item>CreateItemCommand, в RemoveItemCommand и в ItemModel). Не надо заботиться об удалении — этот итем самоудалится, если на него больше ничего не ссылается. Тут даже деструкторы писать не надо.

Всё усложняется, если мы не контролируем то, как хранятся указатели на итемы в ItemModel. Например, если ItemModel унаследован от QGraphicsScene (а там, как известно, итемы хранятся в виде обычных указателей). Как тут быть?

P.S.: по ходу, в примере из документации Qt как раз-таки допущен косяк, из-за которого будет происходить такая утечка памяти — единожды созданные итемы ни при каких условиях не удаляются. Запощу завтра в их багзиллу.

★★★★★

Последнее исправление: Obey-Kun (всего исправлений: 4)

>У меря есть

что на итем не
этот айтем самоудалится

Ты русский язык сначала освой, а потом уж в багзилу пости.

anonymous
()
Ответ на: комментарий от anonymous

и вообще, нашёл к чему придраться в 5 утра.

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от irq

ну в самом-то фреймворке утечки как таковой нет :)

а ссылочек не покажешь, чего там наобсуждали?

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от UVV

Баг очень легко проверить — скомпиль тот пример, добавив для DiagramItem деструктор, выводящий что-нибудь в консоль. Так вот ты в жисть не добьёшься вызова этого деструктора.

Obey-Kun ★★★★★
() автор топика

Совсем недавно ты говорил, что архитектура Undo/Redo в Qt настолько проста и прозрачна, что там нельзя наделать ошибок.

Тут стоит задуматься над жизненным циклом твоих Item-ов. Возможно стоит хранить в командах данные, жизненный цикл которых никак не связан с жизненным циклом Item-а, но при этом из этих данных можно в любой момент восстановить исходный Item, причем этот Item не абы какой, а именно тот, который был в момент добавления/удаления .

Такими данными может быть копия Item, или некий ItemDescriptor, который хранит в упакованном виде информацию об исходном объекте, если такое возможно.

Можно так же сделать shared Item, но тогда не гарантий, что в момент операции Undo/Redo находится именно в таком состоянии, в каком он был в момент создания команды. То есть этот момент надо продумывать отдельно. Зато это может быть проще и более экономично использовать память (но менее надежно).

pathfinder ★★★★
()
Ответ на: комментарий от Gorthauer

> QSharedPointer, нет? Оо И вообще я бы курил в сторону QSharedData

В сцену кладутся обычные поинтеры, так что может быть такая ситуация:

1. Сцена содержит указатель на итем. CreateItemCommand и RemoveItemCommand содержат QSharedPointer на этот итем.

2. Уничтожаются CreateItemCommand и RemoveItemCommand. При этом итем автоматически удаляется, ведь на него больше не ссылается ни одного QSharedPointer.

А ведь в сцене этот итем всё ещё лежит. Сегфолт.

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от pathfinder

> Такими данными может быть копия Item, или некий ItemDescriptor, который хранит в упакованном виде информацию об исходном объекте, если такое возможно.

Не хочу, т.к. у меня в сцену итему кладутся тысячами, и вызывание конструктора для каждого из них при каждом undo/redo — долго. Но, возможно, придётся именно так и делать.

Ещё вариант — держим в сцене QList<QSharedPoint<MyItem> > и изменяем его содержимое при вызове removeItem/addItem. В CreateItemCommand и RemoveItemCommand держим QSharedPoint<MyItem>. Получается, итемы будут самоудаляться, когда точно не нужны больше. Но костыльненько, конечно.

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Gorthauer

> И вообще юзай State Machine Framework :)

Оно-то тут каким боком?

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от pathfinder

Запоминать конкретные состояния и при undo переходить к ним?

Gorthauer ★★★★★
()
Ответ на: комментарий от Obey-Kun

не, не покажу. Ибо точно помню, что факт был замечен ранее, но не уверен, что именно на ЛОРе, хотя если не на ЛОРе, то где?! xD

Могу только посоветовать юзать поиск по ключевым словам QUndo... memory leaks ну и по-русски соответственно.

irq
()

>а там, как известно, итемы хранятся в виде обычных указателей

А если там передавать указатель на QSharedPointer<Item> ?

Yareg ★★★
()

Я бы сделал Item обёрткой с счётчиком ссылок к некоторому PrivateItem.

Yareg ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.