Поиск:

Ответ в темуСоздание новой темы Создание опроса
> [qt] деструкция обьектов 
V
    Опции темы
kapbepucm
Дата 24.10.2013, 16:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 993
Регистрация: 14.6.2007
Где: Латвия

Репутация: нет
Всего: 12



Доброе время суток!
У меня возникла проблема проблема в вероятно неправильном использовании системы деструкторов окон.
Есть такая цепочка дочерних компонентов:
[окно MainWindow] -> [окно Items] -> [окно Item]
У родителя может быть несколько дочерних окон, в свою очередь у дочек также есть дочерние окна. Хотелось бы сделать так, чтобы при закрытии какогото окна удалилось оно само и удалялись все его дочки.
При закрытии MainWindow (при открытых дочках) вылетает ошибка сегментирования. Далее прикладываю все файлы проекта "test"
test.pro
Код
HEADERS   =          mainform.h   items.h   item.h
SOURCES   = main.cpp mainform.cpp items.cpp item.cpp
QT += sql

main.cpp
Код
#include <QApplication>
#include "mainform.h"

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    TMainForm MainForm(0);
    MainForm.show();
    return app.exec();
}

mainform.h
Код
#ifndef MAINFORM_H
#define MAINFORM_H

#include <QWidget>
#include <QtSql>
#include "items.h"

QT_BEGIN_NAMESPACE
class QPushButton;
QT_END_NAMESPACE


class TMainForm : public QWidget
{
    Q_OBJECT

public:
  TMainForm(QWidget *parent = 0);
  QSqlDatabase *db;

private slots:
  void OpenItems();

signals:
  void sigClose();

protected:
  virtual void closeEvent(QCloseEvent *event);

private:
  QPushButton              *OpenItemsButton;
  TItems                   *Items;
};

#endif

mainform.cpp
Код

#include <QtGui>
#include <QtSql>
#include "mainform.h"
#include "items.h"

TMainForm::TMainForm(QWidget *parent) : QWidget(parent)
{
  db = new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE","my_test"));
  db->setDatabaseName(":memory:");
  if (!db->open())
  {
    QMessageBox::critical(this,"db.open()", db->lastError().text(),QMessageBox::Cancel);
    return;
  }
  QSqlQuery query(*db);
  query.exec("create table items (id int primary key, name varchar(20), type int)");
  query.exec("insert into items values(1, 'item1', 1)");
  query.exec("insert into items values(2, 'item2', 1)");

  OpenItemsButton = new QPushButton("OpenItemsButton");
  connect(OpenItemsButton, SIGNAL(clicked()), this, SLOT(OpenItems()) );
  QGridLayout *layout = new QGridLayout();
  layout->addWidget(OpenItemsButton, 0, 0, 0);
  setLayout(layout);

  setWindowFlags(Qt::Window);
}
void TMainForm::OpenItems()
{
  Items = new TItems(this);
  Items->show();
}
void TMainForm::closeEvent(QCloseEvent *event)
{
  db->close();
  db->~QSqlDatabase();
  QSqlDatabase::removeDatabase("my_test");

  emit sigClose();
  event->accept();
}


items.h
Код
#ifndef ITEMS_H
#define ITEMS_H

#include <QWidget>
#include <QtSql>
#include "item.h"

QT_BEGIN_NAMESPACE
class QPushButton;
class TItem;
class TMainForm;
QT_END_NAMESPACE

class TItems : public QWidget
{
  Q_OBJECT

public:
  TItems(TMainForm *parent = NULL);
  ~TItems();
  QSqlRelationalTableModel *model;

private slots:
  void OpenItem();

private:
  QPushButton *OpenItemButton;
  TItem       *Item;

};

#endif

items.cpp
Код

#include <QtGui>
#include "items.h"
#include "mainform.h"

TItems::TItems(TMainForm *parent) : QWidget(parent)
{
  model = new QSqlRelationalTableModel(this, *parent->db);
  model->setTable("items");
  model->select();


  OpenItemButton = new QPushButton("OpenItemButton");

  connect(OpenItemButton, SIGNAL(clicked()), this, SLOT(OpenItem()));

  QGridLayout *layout = new QGridLayout();
  layout->addWidget(OpenItemButton, 0, 0, 0);
  setLayout(layout);

  setWindowFlags(Qt::Window);
  setAttribute(Qt::WA_DeleteOnClose);
}

TItems::~TItems()
{
  QMessageBox::information(this, "TItems", "destroying");
  model->clear();
  model->~QSqlRelationalTableModel();
}

void TItems::OpenItem()
{
  Item = new TItem(this);
  Item->show();
}

item.h
Код

#ifndef ITEM_H
#define ITEM_H

#include <QWidget>

QT_BEGIN_NAMESPACE
class QDataWidgetMapper;
class QLabel;
class QLineEdit;
class QPushButton;
class QSqlRelationalTableModel;
class TItems;
QT_END_NAMESPACE

class TItem : public QWidget
{
    Q_OBJECT

public:
  TItem(TItems *parent = NULL);
  ~TItem();

private:
  QLabel *nameLabel;
  QLineEdit *nameEdit;

  QDataWidgetMapper *mapper;
  QSqlRelationalTableModel * model;
};

#endif

item.cpp
Код

#include <QtGui>
#include <QtSql>
#include "item.h"
#include "mainform.h"

TItem::TItem(TItems *parent) : QWidget(parent)
{
  model = parent->model;

    nameLabel = new QLabel("name");
    nameEdit = new QLineEdit();

    mapper = new QDataWidgetMapper(this);
    mapper->setModel(model);
    mapper->addMapping(nameEdit, model->fieldIndex("name"));

    QGridLayout *layout = new QGridLayout();
    layout->addWidget(nameLabel, 0, 0, 0);
    layout->addWidget(nameEdit, 0, 1, 0);

    setLayout(layout);

    setWindowFlags(Qt::Window);
    setAttribute(Qt::WA_DeleteOnClose);

    mapper->toLast();
}
TItem::~TItem()
{
  QMessageBox::information(this, "TItem", "destroying");
  mapper->clearMapping();
  mapper->~QDataWidgetMapper();
}


Это сообщение отредактировал(а) kapbepucm - 24.10.2013, 16:33


--------------------
(С) kapbepucm
PM MAIL Skype   Вверх
bsa
Дата 24.10.2013, 17:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 24
Всего: 196



А ты отладчиком пробовал вставать на деструктор основного окна?
PM   Вверх
vinter
Дата 24.10.2013, 18:12 (ссылка) |    (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 3
Всего: 56



Код

db->~QSqlDatabase();

Прикалываешься?  smile Дальше не смотрел. Так делать нельзя!(До тех пор пока не поймёшь, когда так делать можно)


--------------------
Мой блог
PM MAIL WWW   Вверх
Cheloveck
Дата 24.10.2013, 18:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1578
Регистрация: 26.7.2008
Где: Тула

Репутация: нет
Всего: 32



Цитата(kapbepucm @  24.10.2013,  17:25 Найти цитируемый пост)
Хотелось бы сделать так, чтобы при закрытии какогото окна удалилось оно само и удалялись все его дочки.

Тебя опередили. Qt делает это самостоятельно. Надо только parent указывать для QWidget (или для QObject).



--------------------
user posted image
PM Jabber   Вверх
kapbepucm
Дата 24.10.2013, 21:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 993
Регистрация: 14.6.2007
Где: Латвия

Репутация: нет
Всего: 12



Цитата(bsa @  24.10.2013,  17:45 Найти цитируемый пост)
А ты отладчиком пробовал вставать на деструктор основного окна? 
Нет, думаю я в нем и не разберусь.

Цитата(vinter @  24.10.2013,  18:12 Найти цитируемый пост)
Так делать нельзя!
Я эту конструкцию добавил, так как только model->~QSqlRelationalTableModel(); спасает от сообщений в stderr типа "QSqlDatabasePrivate::removeDatabase: connection 'my_test' is still in use, all queries will cease to work."
Если это не правильно, то как правильно убить экземляр класса? (Конкретно QSqlRelationalTableModel, так как model->clear(); не отвязывается от моей QSqlDatabase)


Цитата(Cheloveck @  24.10.2013,  18:28 Найти цитируемый пост)
Тебя опередили. Qt делает это самостоятельно. Надо только parent указывать для QWidget (или для QObject).
Родителя указываю. "Дочка" убивается, а "внучка" похоже, что нет.


--------------------
(С) kapbepucm
PM MAIL Skype   Вверх
vinter
Дата 25.10.2013, 08:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 3
Всего: 56



Ты вообще не правильно используешь интерфейс. Тебе не надо хранить db. Тебе просто нужно добавить соединение посредством QSqlDatabase::addDatabase("QSQLITE","my_test"). После этого, когда тебе нужно что-то от базы, ты просто вызываешь QSqlDatabase::database("my_test"). И всё, не надо потом никаких removeDatabase и прочего. Qt об это сам позаботиться.

А в твоём случае нужно использовать 
Код

delete db;

Это будет правильно с точки зрения C++, но не правильно с точки зрения использования Qt, насколько я могу судить. Никогда не вызывай деструктор явно. 

Это сообщение отредактировал(а) vinter - 25.10.2013, 08:58


--------------------
Мой блог
PM MAIL WWW   Вверх
math64
Дата 25.10.2013, 08:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 23
Всего: 72



Вместо
Код

model->~QSqlRelationalTableModel();

нужно делать 
Код

delete model;

а явный вызов деструктора никогда не делай.

Объекты, у которых назначен parent, удаляются автоматически при удалении родителя, но порядок удаления не гарантирован. Если нужен определённый порядок удаления, удаляй вручную в деструкторе.

Наример, перед удалением модели, должны быть удалены объекты её использующие - т.е. перед delete model; нужно удалить TItem вручную или TItem должен коннектиться к сигналу destroyed() модели.
PM   Вверх
kapbepucm
Дата 25.10.2013, 10:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 993
Регистрация: 14.6.2007
Где: Латвия

Репутация: нет
Всего: 12



Спасибо, код изменил. Но проблема видимо ещё где-то осталась. Я в коде проекта попытался отрезать всё, что возможно и оставить "узкое" место. Прилагаю новые файлы.
test.pro
Код
HEADERS   = mainform.h
SOURCES   = main.cpp

main.cpp
Код
#include <QApplication>
#include "mainform.h"

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    TMainForm MainForm(0);
    MainForm.show();
    return app.exec();
}

mainform.h
Код
#ifndef MAINFORM_H
#define MAINFORM_H

#include <QtGui>

class TMainForm : public QWidget
{
  Q_OBJECT
  public:
    TMainForm(QWidget *parent = 0) : QWidget(parent)
    {
      QPushButton *OpenChildButton = new QPushButton("OpenChildButton");
      connect(OpenChildButton, SIGNAL(clicked()), this, SLOT(OpenChild()) );
      QGridLayout *layout = new QGridLayout();
      layout->addWidget(OpenChildButton, 0, 0, 0);
      setLayout(layout);

      setWindowFlags(Qt::Window);
      if (parent) setAttribute(Qt::WA_DeleteOnClose);
    }
    ~TMainForm()
    {
      QMessageBox::information(this, "TMainForm", "Destruction");
    }
  private slots:
    void OpenChild()
    {
      TMainForm * Child = new TMainForm(this);
      Child->show();
    }
};

#endif


И так, алгоритм вызова ошибки сегментирования:
  • запускаем исполняемый файл
  • жмём кнопку вызова дочернего окна
  • на дочернем окне так же жмем кнопку вызова дочернего окна
  • закрываем первое окно
результат: закрываются первые 2 окна и вылет приложения в ошибку.


--------------------
(С) kapbepucm
PM MAIL Skype   Вверх
vinter
Дата 25.10.2013, 10:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 3
Всего: 56



Убери
Код

setAttribute(Qt::WA_DeleteOnClose);

Я не знаю как этот аттрибут работает, но, подозреваю, что он предназначен для виджетов верхнего уровня и может из-за него происходит двойное удаление. Т.е. все твои "дети" итак будут удалены: у них есть родитель для этого. Чтобы точно понять, что происходит - надо смотреть код Qt.


--------------------
Мой блог
PM MAIL WWW   Вверх
kapbepucm
Дата 25.10.2013, 11:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 993
Регистрация: 14.6.2007
Где: Латвия

Репутация: нет
Всего: 12



Если убрать этот атрибут, то при закрытии окна его потомки не закрываются автоматически. (Окно закрывается но продолжает висеть в памяти) Исключением является первое окно- если его закрыть убивается вся цепочка детей. Зато ошибки сегментирования нет. Видимо, дополнительно надо по примеру math64 посылать "суицидальный" сигнал потомку. Как это сделать проще всего?

Это сообщение отредактировал(а) kapbepucm - 25.10.2013, 11:42


--------------------
(С) kapbepucm
PM MAIL Skype   Вверх
vinter
Дата 25.10.2013, 11:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 3
Всего: 56



Не нужно гадать, нужно разбираться в проблеме. Скорее всего это поведение связано с Qt::Window, т.к. это обычно атрибут независимого окна, а не дочернего. Видимо с этим данное поведение и связано. Зачем тебе этот атрибут? А если нужен, то зачем тебе родители? Ну а если всё нужно, то у виджетов есть close() слот. При создании потомка подсоединяй к этом слоту некий сигнал, который и вызывай(emit) из closeEvent родителя. Но, по моему проще в этом случае создавать детей без родителя и в closeEvent() делать close() дочернему окну, за которым будет следовать delete. 



--------------------
Мой блог
PM MAIL WWW   Вверх
kapbepucm
Дата 29.10.2013, 17:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 993
Регистрация: 14.6.2007
Где: Латвия

Репутация: нет
Всего: 12



Я сделал по алгоритму vinter, то есть 
  • при закрытии окна посылается сигнал close() к потомку
  • при закрытии окна также активируется свой слот deleteLater()
на дочерних окнах всё работает, как и хотелось. Закрывая родителя, закрывается, а затем удаляется потомок, потом удаляется и сам родитель. Немного проработать осталось над последовательностью. Проблема только в главном первом окне. Почему для главного окна нельзя использовать deleteLater? (приводит к падению приложения). Я урезал ещё больше код, оставив самое узкое место:
Код
#include <QApplication>
#include <QtGui>

class TMainForm : public QWidget
{
Q_OBJECT
public:
  TMainForm(QWidget *parent = 0) : QWidget(parent)
  {
    connect(this, SIGNAL(sigClosed()),this, SLOT(deleteLater()) );
  }
signals:
  void sigClosed();
protected:
  virtual void closeEvent(QCloseEvent *event)
  {
    emit sigClosed();
    event->accept();
  }
};

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    TMainForm MainForm(NULL);
    MainForm.show();
    return app.exec();
}
Проблема конечно решена, путём неиспользования deleteLater() на главном окне, но интересует суть ошибки.

Это сообщение отредактировал(а) kapbepucm - 29.10.2013, 17:18


--------------------
(С) kapbepucm
PM MAIL Skype   Вверх
vinter
Дата 29.10.2013, 17:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Explorer
****


Профиль
Группа: Завсегдатай
Сообщений: 2735
Регистрация: 1.4.2006
Где: Н.Новгород

Репутация: 3
Всего: 56



Потому что твое главное окно не является указателем

Добавлено через 2 минуты и 7 секунд
Вообще, мне не нравится когда объект сам себе посылает deleteLater() это должно делать ответственное лицо(родитель, например), но тебе виднее, конечно.


--------------------
Мой блог
PM MAIL WWW   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С/С++: Кроссплатформенное программирование, QT/Gtk+/wxWidgets"
JackYF
Любитель
  • В заголовке темы в квадратных скобках обозначьте используемую вами библиотеку, например: [QT],[GTK],[wx].
  • Если вопрос актуален только для некоторой версии библиотеки, либо, если вы пользуетесь не самой последней версией, укажите это. Например: [QT4], [GTK2].
  • Все начинающие изучать Qt - не забудьте зайти сюда.
  • Проставьте несколько ключевых слов темы, чтобы её можно было легче найти.
  • В вопросе укажите полную версию версию библиотеки, а также все дополнительные используемые программные пакеты.
  • Не забывайте пользоваться кнопкой "Код".
  • Телепатов на форуме нет! Задавайте чёткий, конкретный и полный вопрос. Указывайте полностью ошибки компилятора и компоновщика.
  • Новое сообщение должно иметь прямое отношение к тематике этого раздела. Флуд, флейм, оффтопик запрещены.
  • Категорически запрещается обсуждение вареза, "кряков", взлома программ и т.д.

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, JackYF, Любитель.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | С/С++: Кроссплатформенное программирование, Qt/Gtk+/wxWidgets | Следующая тема »


 




[ Время генерации скрипта: 0.0558 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.