LINUX.ORG.RU

Почему сегфолт в деструкторе?

 ,


0

2

Имею класс, в котором создается виджет таблицы QTableWidget, указатель на который хранится в свойстве table:

table=new QTableWidget(0, colsName.count(), this);
table->setHorizontalHeaderLabels(colsName);


В процессе работы класса в таблицу добавляются строки. Ячейки строк заполняются QTableWidgetItem * или виджетами, вставляемыми через QTableWidget::setCellWidget();

В деструкторе необходимо пробежать всю таблицу и удалить итемы и вставленные виджеты.

Я это делаю так:

Downloader::~Downloader()
{
  // Удаляется содержимое таблицы
  for(int i=0; i<table->rowCount(); i++) // <-- Тут сегфолт!
  {
    delete table->item(i, downloadReferenceCol);
    delete qobject_cast<QProgressBar *>( table->cellWidget(i, downloadReferenceCol) );
  }

  delete table;
  delete cancelButton;
}


Но получаю сегфолт на строке с циклом при выполнении самой первой итерации.

Скриншот:

http://i.piccy.info/i9/053bea8edbb2549fea811c231e4af339/1452982758/389884/825...

Видно, что table существует, и содержит какой-то адрес. Но выражение table->rowCount() почему-то не высчитывается. И получается сегфолт.

Вопрос: из-за чего появляется такой сегфолт?

★★★★★

Вот скажи мне, как регистрант онанимусу, ты ведь имеешь 5 звезд на ЛОРе, вопросы по крестам и Qt стал задавать много месяцев назад, но почему-то они все уровня «я вчера начал кодить». Что за хрень?

anonymous
()

Кроме сегфолта - у вас прога все равно упадет, так как вы увеличиваете i, а rowCount все меньше и меньше. Ошибка первокурсника.

Видно, что table существует, и содержит какой-то адрес

Что? Видно что table указывает на память, но это не значит что эта память была еще доступна.

В общем нужно больше информации.

anonymous
()

Запихивать QProgressBar в QTableWidget также не стоит. Мало того что озу жрать будет, так еще и тормоза отрисовки появятся.

anonymous
()

Да, вот этот каст delete qobject_cast<QProgressBar *> офигенно нужен. Как несколько десятилетий просто с

Base* bp = new Derived; // Upcast delete bp;

жили...

anonymous
()

А почему ты не очищаешь свою таблицу с помощью специально предназначенного для этого слота clear()?

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

Мне, как другому анонимусу интересно другое. Это далеко не первый пост товарища регистранта, который демонстрирует его полное непонимание работы с памятью. Ему на это неоднократно указывали, но он нихрена не внял и продолжает из раза в раз городить херню. Вопрос: через сколько постов он наконец перестанет валять дурака и начнет писать на вижуалбейсике?

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

Вот скажи мне, как регистрант онанимусу, ты ведь имеешь 5 звезд на ЛОРе, вопросы по крестам и Qt стал задавать много месяцев назад, но почему-то они все уровня «я вчера начал кодить». Что за хрень?

Много месяцев назад? Бери больше - лет!

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

Если посмотреть на гитхаб, то можно увидеть, что зачастую у меня проходит пара недель или даже месяц между моментом, когда внесено изменение в код, и моментом, когда код вообще начал компилироваться. Попробуй поработать в таком режиме. Это ужасно.

Я попадаю «в поток» либо в отпуске, либо на новогодних праздниках. Две недели уходит на то, чтобы нормально вспомнить что у меня там понаделано и уместить структуру в голове. К концу двух недель незанятости я наконец то вспоминаю что к чему в проекте, немножко вспоминаю как что делать в C++. Ирония в том, что отпуск дают только на 2 недели и новогодние праздники тоже примерно 2 недели. К моменту, когда я готов что-то делать, на меня сваливается работа, за которую мне платят деньги, и я быстро съезжаю к нуливому уровню.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от XMs

А почему ты не очищаешь свою таблицу с помощью специально предназначенного для этого слота clear()?

Потому что в документации не написано, что будут удалены объекты, помещенные в таблицу:


void QTableWidget::clear()

Removes all items in the view. This will also remove all selections and headers. If you don't want to remove the headers, use QTableWidget::clearContents(). The table dimensions stay the same.



Removes all items in the view - не говорит о том, что item будут delete. Они просто будут romove из view.

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

К концу двух недель незанятости я наконец то вспоминаю что к чему в проекте, немножко вспоминаю как что делать в C++.

Сначала усложняешь себе жизнь ручным управлением памятью, а потом жалуешься на сложность.

Постарайся писать так, чтобы в коде не было явных delete. (Ну разве только в каких-нибудь mixin'ах типа RefCounted.)

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

Кроме сегфолта - у вас прога все равно упадет, так как вы увеличиваете i, а rowCount все меньше и меньше. Ошибка первокурсника.

С чего бы это rowCount все меньше и меньше. Удаляются только объекты, вставленные в ячейки таблицы. Размер таблицы не меняется.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от i-rinat

Сначала усложняешь себе жизнь ручным управлением памятью, а потом жалуешься на сложность.
Постарайся писать так, чтобы в коде не было явных delete.

Да я бы рад, но объекты Qt спроектированы так, что без ручного управления не обойтись.

Вот топиковый QTableWidget. Чтобы установить ячейку, надо создать item, и засунуть указатель на него через setItem(). Или если засовывать виджет в таблицу, то тоже нужно его создать и передать указатель.

Но потом же нужно как-то эти нагенерированные объекты удалить. Хорошо что QTableWidget как раз и является «базой» с указателями на нагенерированные объекты. Я и удаляю, беря указатели из QTableWidget.

С QTableWidget можно работать как-то по-другому?

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

Да я бы рад, но объекты Qt спроектированы так, что без ручного управления не обойтись.

Можно исхитриться. Оборачиваешь при создании объекты в unique_ptr. Если куда-то нужен просто указатель, а владение остаётся за тобой, забираешь указатель через .get(). Если нужно передать владение, забираешь через .release(). В обоих случаях delete не нужно вызывать явно.

Вот топиковый QTableWidget.

Но потом же нужно как-то эти нагенерированные объекты удалить.

Враз гуглится: http://doc.qt.io/qt-5/qtablewidget.html#setItem. «The table takes ownership of the item.»

i-rinat ★★★★★
()
Ответ на: комментарий от Xintrea

В общем нужно больше информации.

Чтобы словить, надо запустить прогу, добавить запись, выделить в браузере текст с картинкой, скопировать и вставить в текст записи.

Откроется окно закачки. Закрыть окно крестиком, и получить сегфолт.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от i-rinat

«The table takes ownership of the item.»

Вот ты знаешь, мне это ни о чем не говорит. Я, конечно, могу предположить, что под владельцем (ownership) понимается владелец ресурса (хотя не факт, кто его знает, что аффтары имели в виду). И владельцы бывают разные. Есть которые удалят ресурс в своем деструкторе, а может быть и не удалят, ведь об этом нигде не написано.

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

мне это ни о чем не говорит

Что очень плохо. Владелец — это тот, кто управляет временем жизни объекта. Этого разве в учебниках нет?

И владельцы бывают разные. Есть которые удалят ресурс в своем деструкторе, а может быть и не удалят

Если не удаляет, значит он не владелец, а одолжил на время попользоваться. Задача программиста проследить, чтобы взявший на время закончил играться с объектом до того, как владелец его удалит.

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

Да, вот этот каст delete qobject_cast<QProgressBar *> офигенно нужен. Как несколько десятилетий просто с
Base* bp = new Derived; // Upcast delete bp;

Согласно документации, QTableWidget::cellWidget() возвращает тип QWidget *. А мне нужно удалять QProgressBar *.

И я не понимаю, как это так лихо в Base (пусть и по указателю) помещается производный Derived со всеми его дополнительными полями? Что это за контроль типов такой?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от i-rinat

Владелец — это тот, кто управляет временем жизни объекта. Этого разве в учебниках нет?

У меня семь книжек по C++:

Самоучитель C++ Г. Шилдт (самая адекватная)
Язык программирования C++ С. В. Глушаков
Язык Си++ В. В. Подбельский (самая неадекватная)
C++ для чайников Р. Девис
C++ изнутри Р. Вайнер
От Си к Си++ Д. Рассохин
От Си к Си++ Е. Козелл

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

Если знаешь книгу на русском языке, в которой эта тема полно освещена, скажи название, я себе ее закажу.

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

Тебе правильно указывают на документацию, где сказано take ownership. В Qt сделано практически все, чтобы не было бесхозных объектов. По поводу удаления объекта производного, через указатель на базовый: а как же паттерн шаблонный метод? Получаешь указатель на базовый, используешь и удаляешь. И плевать, какая реализация была.

Понятно, что хобби не работа и времени нифига нет, но лучше прочесть пару книжек и потом потихоньку делать, как надо, чем мудехатсо по форумам. Времени сэкономишь вагон.

--

Первый анонимус

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

Что это за контроль типов такой?
Это же шутка, да?

Я вообще не понимаю, как может работать такая конструкция:

Base* bp = new Derived;

Создастся расширенный объект. Но указатель будет храниться как указатель на базовый объект. Потом delete каким-то волшебным образом узнает, что указатель с базовым типом на самом деле указывает на расширенный объект. И корректно удалит расширенный объект.

Каким образом delete выводит тип Derived *, если ей передается указатель с типом Base * ?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от thecre

С какого перепугу для указателя на Base* будет вызываться виртуальный метод расширенного класса Derived*? Обратное я могу себе представить, но это - нет.

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

Тебе правильно указывают на документацию, где сказано take ownership. В Qt сделано практически все, чтобы не было бесхозных объектов.

Упростил деструктор до такого примитива:

Downloader::~Downloader()
{
  delete table; // Объекты, содержащеся в таблице, будут удалены автоматически, так как table их владелец
  delete cancelButton;
}


И имею сегфолт на delete table.

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

Ну вот такой динамический полиморфизм в крестах. И да, компилятор знает, что это объект производного класса и его размер в памяти. Единственное требование, чтобы деструктор виртуальным был, но Qt знают, что кувиджет всякие перцы будут наследовать и вписали слово вирчуал для тебя

--

Первый анонимус

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

Дык тыж в конструкторе QTableWidget указал родителя. Один раз он удалит таблицу, один раз -ты. Итого 2 раза.

--

Первый анонимус

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

Дык тыж в конструкторе QTableWidget указал родителя. Один раз он удалит таблицу, один раз -ты. Итого 2 раза.

Да япона матерь. Пойду спать.

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

С какого перепугу для указателя на Base* будет вызываться виртуальный метод расширенного класса Derived*? Обратное я могу себе представить, но это - нет.

А нахрена тогда вообще придумали виртуальные методы? Таблица виртуальных методов находится в памяти объекта и за тем, какой деструктор вызвать, следит уже не компилятор, а рантайм.

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

Потому что double free.

В Qt нет необходимости явно удалять дочерние QObject-based объекты, если в конструкторе им был указан parent (у тебя в примере последний параметр).

table=new QTableWidget(0, colsName.count(), this);
grondek
()
Ответ на: комментарий от Xintrea

delete cancelButton

А для объектов с законнекченными сигналами/слотами есть deleteLater.

Suigintou ★★★★★
()
Последнее исправление: Suigintou (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Сон не поможет. По-доброму соаетую же, перейди на вижуалбейсик, не мучайся.

anonymous
()

Твой деструктор не нужен, вообще, просто не забывай указывать родительский объект.

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

Не надо таких советов. Если парент сдох и утащил за собой чайлда, то будет двойное удаление и беда. Да, обратный порядок прокатит, тк кутешечники позаботились о криворуких кодерах, но зачем писать доп. строчки бессмысленного кода. В 99% случаев delete в qt не нужен, в остальных случаях указатель надо брать в scoped pointer.

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

И я же не издеваюсь, когда про вижуалбейсик говорю. Допустить двойное освобождение памяти, да ещё в таком виде, что в глаза бросается (но ты этого не видишь), можно лишь не понимая как это всё работает. Есть же QtScript, зачем мучаться с С++.

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

Это не совет. Любой объект, не зависимо от того, указан у него владелец или нет, может быть удален руками (например, через delete). Никакого двойного удаления не произойдет, т.к. удаляемый объект выпишется из списка «детей» своего «папки».

Если парент сдох и утащил за собой чайлда, то будет двойное удаление и беда.

Если парент сдох, то чайлд сдох еще раньше. Какая беда?

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

Да. Этим пользуюсь для удаления отдельных виджетов в процессе работы.

Suigintou ★★★★★
()

Если сделать QTableWidget parent'ом всех виджетов, засовываемых в ячейки, то, при удалении QTableWidget, все дети будут автоматически удалены.

Это верно для любых QWidget.

Y ★★
()

Тебе хватит и delete table; delete cancelButton;

Все виджеты, что ты закинул в таблице уже становятся его «детьми». Он сам их удалит, когда вызовется у него деструктор. Это же Qt.

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

Мы обсуждаем удаление объекта с заданным парентом - его удалять можно.

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

Зачем о_О ??? Qt предлагает хороший способ полуавтоматического удаления, не надо лезть в него без необходимости.

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

Removes all items in the view - не говорит о том, что item будут delete. Они просто будут romove из view

QTableWidget — это просто обвязка над QTableView/QTableModel. Соответственно, в слоте просто дёргается соответствующий метод у модели:

void QTableWidget::clear()
{
    Q_D(QTableWidget);
    selectionModel()->clear();
    d->tableModel()->clear();
}

А про модель написано на QtCentre: http://www.qtcentre.org/threads/28470-Does-QTableWidget-clear()-delete-pointers

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

Если знаешь книгу на русском языке

Так сложилось, что я ни одного учебника по C++ так и не прочитал. Только tutorial какой-то. Но выяснилось, что нового ничего там нет, всё это я уже узнал из обрывочных заметок в интернете. Так что с названиями книг я тебе не смогу помочь.

никакой теории о владельце ресурса

Не помню, чтобы где-то явно говорилось, что владение это ответственность за вызов деструктора. Контекст, что ли. Как если в тексте говорится о языке программирования, но нет явного определения. «HQ9++ это объектно-ориентированное расширение HQ9+. В нём добавлена новая команда, ++, которая увеличивает счётчик два раза и создает объект. В соответствии с принципом сокрытия информации, доступ к этому объекту невозможен.»

Ну и учи английский. Лексика, которая используется в технической литературе, гораздо проще литературного языка. Но даже с ней количество информации возрастает в десять раз. Понятное дело, читать со словарём поначалу напряжно. Но оно того стоит.

i-rinat ★★★★★
()
Ответ на: комментарий от grondek

В Qt нет необходимости явно удалять дочерние QObject-based объекты, если в конструкторе им был указан parent (у тебя в примере последний параметр).

Непонятно вот что. У меня класс Downloader - производный от QDialog.

В C++ вначале срабатывает деструктор производного класса (в моем случае это Downloader). Потом, если производный класс содержал объекты как свойства - деструкторы включенных классов (в моем случае это QTableWidget и QPushButton). И только потом срабатывает деструктор базового класса QDialog.

Так вот, если в деструкторе ~Downloader() сделать

delete table;


то ошибки именно в деструкторе ~Downloader() возникать не должно. Она должна возникать позже, в деструкторе ~QTableWidget().

А возникает она именно в ~Downloader(). Почему так происходит?

Xintrea ★★★★★
() автор топика

Не знаю ничего про Qt, но это бросилось в глаза:

for(size_t i = 0, size = table->rowCount(); i < size; i++)
{
     // если это приводит к удалению элемента из таблицы и таблица ресайзится
    delete table->item(0, downloadReferenceCol);

    // если удаляется память, выделенная под элемент, а таблица не меняется
    delete table->item(i, downloadReferenceCol); 
andreyu ★★★★★
()
Последнее исправление: andreyu (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Самоучитель C++ Г. Шилдт (самая адекватная)

Была у меня эта «адекватная». Программировать она вообще не учит, и язык там дается кое-как (но таки полиморфизм там должен быть, так что дело не только в книгах).

Смотри Майерса-Саттера, если читать любишь и английский не знаешь. Но лучше знать.

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

Была у меня эта «адекватная».

Ты еще других не видел.


Но таки полиморфизм там должен быть, так что дело не только в книгах

Да есть он там, есть. Я сейчас пересмотрел главу, и понял, что раньше я неправильно понимал работу указателей при динамическом полиморфизме.

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

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

И я ума не приложу, с какой целью люди пишут так:

Base* bp = new Derived;


Вместо естественного

Derived* bp = new Derived;


Зачем такая путанница нужна?

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