Russian Qt Forum

Qt => Общие вопросы => Тема начата: 0v.v0 от Апрель 23, 2016, 10:05



Название: Qt и управление памятью
Отправлено: 0v.v0 от Апрель 23, 2016, 10:05
Не нашёл похожей темы, поэтому начнём новую.
В общем, вопрос такой: как Qt Framework решает проблемы языка C++, связанные с управлением памятью, если не использует умные указатели?
1. Структура, которая владеет вложенными объектами, управляется через родителей QObject, тут всё понятно.
2. Если несколько объектов должны владеть объектом, уже начинаются вопросы. Например, 2 представления используют одну модель. Кто её будет освобождать? Пока что на примете единственный вариант - создавать эту модель в классе, который владеет всеми представлениями.
3. Но если двум объектам нужно получить слабый указатель друг на друга, идеи заканчиваются. Для вызова методов можно наклепать сигналов и слотов и соединять ближайшим общим родителем. А если нужно опросить чьи-то данные? Простой указатель не будет знать, можно ли обращаться по своему адресу, или память под ним была освобождена.


Название: Re: Qt и управление памятью
Отправлено: Old от Апрель 23, 2016, 10:09
А почему не использовать умные указатели?  ::)


Название: Re: Qt и управление памятью
Отправлено: 0v.v0 от Апрель 23, 2016, 10:30
Речь идёт о том, что объект добавляется в компоновщик, который автоматически будет его удалять. Если одновременно обернуть его shared_ptr, не возникнет проблем с двойным удалением? А может ещё и висяки образуются, если у объекта несколько владельцев и компоновщик сработает раньше деструктора указателей.


Название: Re: Qt и управление памятью
Отправлено: Old от Апрель 23, 2016, 10:49
Речь идёт о том, что объект добавляется в компоновщик, который автоматически будет его удалять. Если одновременно обернуть его shared_ptr, не возникнет проблем с двойным удалением? А может ещё и висяки образуются, если у объекта несколько владельцев и компоновщик сработает раньше деструктора указателей.
Для виджетов можно не использовать умные указатели (хотя ни каких проблем их использование не вызовет). Для виджетов мы вынуждены задавать парент не только для автоматического удаления детей, но и для правильного размещения виджетов.
А вот для невидимых данных использщовать умные указатели IMHO предпочтительней.


Название: Re: Qt и управление памятью
Отправлено: Igors от Апрель 23, 2016, 11:13
2. Если несколько объектов должны владеть объектом, уже начинаются вопросы. Например, 2 представления используют одну модель. Кто её будет освобождать? Пока что на примете единственный вариант - создавать эту модель в классе, который владеет всеми представлениями.
Здесь не видно возражений против shared_ptr (QSharedPointer), он напрашивается.

3. Но если двум объектам нужно получить слабый указатель друг на друга, идеи заканчиваются. Для вызова методов можно наклепать сигналов и слотов и соединять ближайшим общим родителем. А если нужно опросить чьи-то данные? Простой указатель не будет знать, можно ли обращаться по своему адресу, или память под ним была освобождена.
Я обычно делаю контейнеры для обоих. Пример: есть неск окон, в каждом картинки (возможно часть повторяется). Каждая картинка хранит указатели на окна в которых она используется, каждое окно - указатели используемых картинок. Сами картинки хранятся в своем контейнере. Теперь можно удалять как картинку так и окно, действия при удалениях очевидны.

..как Qt Framework решает проблемы языка C++, связанные с управлением памятью,
Я бы не назвал это "проблемой языка"  :)


Название: Re: Qt и управление памятью
Отправлено: 0v.v0 от Апрель 23, 2016, 12:11
Привожу конкретный пример. Предположим, что в объекте А создан объект "о", а в объекте В указатель "р". Сымитируем ситуацию удаления через контейнер объектов:

Код:
QObject *o = new QObject();
QObject *o1 = new QObject();
QObject *o  = new QObject(o1);
qDebug() << o;
QSharedPointer<QObject> p(o);
delete o1;
qDebug() << p.data();

Если запустить это пример, получим:
QObject(0x8ffdb8)
Программа неожиданно завершилась.


Название: Re: Qt и управление памятью
Отправлено: Igors от Апрель 23, 2016, 12:50
Если запустить это пример, получим:
QObject(0x8ffdb8)
Программа неожиданно завершилась.
Давайте в борщ свалим котлеты и кашу, а сверху еще зальем компотом, будете это кушать?  (впрочем недавно слышал что один французский аристократ так и делал).

Вы задействовали Qt parent-child которая предполагает что чайлды - простецкие указатели распределенные в куче (см deleteChildren). А потом добавили еще шареный указатель (который обязан удалять только сам) - ну конечно рухнет, т.к. 2 разные системы столкнулись лбами. Выберите что-то одно, по крайней мере для каждого экземпляра объекта.

Привожу конкретный пример.
В том-то и дело что никакой конкретики пока нет. Похоже Вы хотите найти какой-то общий "наилучший" метод чтобы применять его всегда и везде. Это не ново, но удачных попыток я не видел. "Сделаем все щаред" (типа избавиться от удаления вообще) обычно наталкивается на то что нередко Qt требует родителя. Возьмите живую конкретную задачу, скорее всего выяснится что изобретать ничего и не надо, т.е. проблема надумана.


Название: Re: Qt и управление памятью
Отправлено: 0v.v0 от Апрель 24, 2016, 10:55
Поймите меня правильно, я ищу не единственный метод, а грамотную систему этих методов.
Вы правы, давайте попробуем сообразить пример исходя из задачи.

Дано: требуется создать два виджета, которые указывают друг на друга. В каждом из них кнопка и текстовое поле. По нажатии на кнопку окно сравнивает своё и чужое текстовое поле и если текст одинаковый - самоуничтожается,  иначе присваивает своей строке новый текст.

Стартовый код аварийно завершается.
Код:
#include <QtWidgets>

class MegaWidget : public QWidget
{
public:
    MegaWidget()
    {
        QVBoxLayout *layoutMain = new QVBoxLayout();
        {
            button = new QPushButton("close if equals");
            connect(button, &QPushButton::clicked, this, &MegaWidget::closeIfEquals);
            layoutMain->addWidget(button);

            line = new QLineEdit("text");
            layoutMain->addWidget(line);
        }
        setLayout(layoutMain);
        show();
    }

    void setOther(MegaWidget *other)
    {
        this->other = other;
    }

    QString getText()
    {
        return line->text();
    }

public slots:
    void closeIfEquals()
    {
        if (line->text() == other->getText())
            delete this;
        else
            line->setText(other->getText());
    }

private:
    MegaWidget *other;

    QPushButton *button;
    QLineEdit *line;
};

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    MegaWidget* w1 = new MegaWidget();
    MegaWidget* w2 = new MegaWidget();

    w1->setOther(w2);
    w2->setOther(w1);

    return app.exec();
}


Название: Re: Qt и управление памятью
Отправлено: Bepec от Апрель 24, 2016, 11:08
ОМФГ... delete this это сильно написано :D


Название: Re: Qt и управление памятью
Отправлено: Igors от Апрель 24, 2016, 12:35
Стартовый код аварийно завершается.
У меня вылетает при втором тычке (первый отрабатывает нормально). Да, если храним указатель, то нужно заботиться чтобы он был валиден. Для простых случаев хорош QPointer, напр
Код
C++ (Qt)
public slots:
   void closeIfEquals()
   {
if (other.isNull()) return;
       if (line->text() == other->getText())
           delete this;
       else
           line->setText(other->getText());
   }
 
private:
   QPointer<MegaWidget> other;
 
В более сложных случаях все равно придется заняться сигналами/оповещениями использующих указатель. Напр по-хорошему после первого закрытия нужно задизаблить кнопарь в оставшемся окне, напр
Код
C++ (Qt)
void closeIfEquals()
{
  if (other && (line->text() == other->getText())) {
    other->setOther(0);  
    delete this;
...
}
Более серьезная проблема - (де)сериализация указателей которыми объект не владеет.

ОМФГ... delete this это сильно написано :D
Никаким правилам языка это не противоречит, продолжаете использовать после этого - сами виноваты. Помню даже SDK с виртуальным методом DeleteThis (реализация delete this)


Название: Re: Qt и управление памятью
Отправлено: Bepec от Апрель 24, 2016, 13:53
Угу, любое усложнение программы с сигналами и мы получаем большую бомбу замедленного действия.
Конечно сами виноваты. А лучше так вообще не делать :D


Название: Re: Qt и управление памятью
Отправлено: kambala от Апрель 24, 2016, 15:34
может лучше deleteLater() вместо delete this?


Название: Re: Qt и управление памятью
Отправлено: Igors от Апрель 24, 2016, 16:56
может лучше deleteLater() вместо delete this?
Допустим если тексты равны надо удалить др окно (а не то где мы нажимали). Тогда
Код
C++ (Qt)
void closeIfEquals()
{
  if (other && (line->text() == other->getText())) {
    delete other;  
    other = 0;
...
Вряд ли этот текст вызовет какие-то вопросы, что же изменилось если this вместо other? Да ничего :) А вдруг "чего-то случится", и мы вызовем метод разрушенного this? Так ведь с тем же успехом мы можем позвать метод разрушенного other - почему это никого не заботит? :)

Наоборот, deleteLater потенциально опасен и должен применяться только когда нет др выхода.


Название: Re: Qt и управление памятью
Отправлено: lit-uriy от Апрель 24, 2016, 19:30
"Наоборот, deleteLater потенциально опасен и должен применяться только когда нет др выхода." В Доке написано обратное.

т.к. он ждёт, когда закончатся все события для объекта.

П.С.
Некоторые люди не могут жить без проблем, поэтому всеми силами пытаются их себе создать.


Название: Re: Qt и управление памятью
Отправлено: Old от Апрель 24, 2016, 21:08
Дано: требуется создать два виджета, которые указывают друг на друга.
На самом деле для решения этой задачи не нужны указатели друг на друга.
Каждый виджет при нажатии кнопки должен посылать сигнал с текстом в качестве аргумента. Тогда, вам нужно просто законектить сигнал одного виджета со слотом в другом. А при разрушении виджета связи разрываются автоматически.


Название: Re: Qt и управление памятью
Отправлено: 0v.v0 от Апрель 24, 2016, 21:11
Тогда если подвести итог, имеем что-то такое (если нужно - поправляйте):
1. Графика вся контролируется контейнерами виджетов, модели через QScopedPointer.
2. Множественного контроля графики быть не может, потому что все виджеты располагаются иерархично, а модели используют QSharedPointer.
3. Слабые ссылки в виджетах имитируются сигналами, модели могут спокойно использовать QWeakPointer.


Название: Re: Qt и управление памятью
Отправлено: Bepec от Апрель 24, 2016, 21:49
Если итог, можете делать как хотите, это C++. Хоть всё на чистые указатели перепишите :D


Название: Re: Qt и управление памятью
Отправлено: Igors от Апрель 25, 2016, 08:17
Тогда если подвести итог, имеем что-то такое (если нужно - поправляйте):
1. Графика вся контролируется контейнерами виджетов, модели через QScopedPointer.
2. Множественного контроля графики быть не может, потому что все виджеты располагаются иерархично, а модели используют QSharedPointer.
3. Слабые ссылки в виджетах имитируются сигналами, модели могут спокойно использовать QWeakPointer.
Если речь идет (а она идет) об UI, то применение вумных указателей ограничено. Виджеты могут передаваться от одного парента другому и парент может вызвать delete. Поэтому shared пролетает, а без него нет смысла и в weak. Но шаг в сторону (напр "модель"), и все по-другому, умные указатели могут быть вполне уместны.

Вообще часто возникает ситуация когда объект использует др объект, но не владеет им, т.е. не отвечает за его создание/удаление. При этом weak не всегда подходит т.к. используемый может и не иметь явного владельца. Тут мне нравится решение с ID. Храним напр qint64 и всякий раз запрашиваем указатель на используемый объект по этому ID. Появляется много интересных/элегантных решений, особенно для (де)сериализации и undo, где с указателями долго пыхтеть.


Название: Re: Qt и управление памятью
Отправлено: __Heaven__ от Апрель 28, 2016, 10:33
2. Если несколько объектов должны владеть объектом, уже начинаются вопросы. Например, 2 представления используют одну модель. Кто её будет освобождать? Пока что на примете единственный вариант - создавать эту модель в классе, который владеет всеми представлениями.

Память из-под модели будет высвобождать её родитель. Вполне вероятно, что родителем можно сделать виджет, в котором находятся ваши представления.