Странное поведение объекта на QGraphicsScene
От: XOOIOOX  
Дата: 08.05.20 18:39
Оценка:
Есть объект на сцене, который сам себя добавляет на нее и сам себя удаляет:

Monster::Monster(CentralDataStruct& data) : QObject(nullptr), QGraphicsRectItem(nullptr), centralData(data)
{
    setRect(0, 0, TileSize, TileSize);
    centralData.scene->addItem(this);
    destroyTimer = new QTimer(this);
    connect(destroyTimer, SIGNAL(timeout()), this, SLOT(destroySlot()));
}

Monster::~Monster()
{
    scene()->removeItem(this);
}

void Monster::advance(int phase)
{
    if (!phase)
    {
        emit destroySignal();
        //destroyTimer->start(1);
    }
}

void Monster::destroySlot()
{
    emit destroySignal();
}

Хранится в std::list, но это не принципиально.

Странность заключается в том, что если он из метода advance посылает сигнал вовне (там очищаем список, скажем), то приключается крэш — сцена обращается по невалидному указателю. Если же посылать сигнал через таймер, даже с минимальной задержкой (закомменченная строка), все обрабатывается нормально, крэша нет. В обоих случаях снаружи видно, что объект удаляется со сцены.
Извне, опять же, объект удаляется корректно.

Как так получается? В чем засада?

https://www.youtube.com/watch?v=-Ui4prpCZ0w
Re: Странное поведение объекта на QGraphicsScene
От: K13 http://akvis.com
Дата: 09.05.20 06:38
Оценка: 6 (1)
XOO>Странность заключается в том, что если он из метода advance посылает сигнал вовне (там очищаем список, скажем), то приключается крэш — сцена обращается по невалидному указателю. Если же посылать сигнал через таймер, даже с минимальной задержкой (закомменченная строка), все обрабатывается нормально, крэша нет. В обоих случаях снаружи видно, что объект удаляется со сцены.
XOO>Извне, опять же, объект удаляется корректно.

XOO>Как так получается? В чем засада?


а коннект какой был? по умолчанию -- авто, который для объектов принадлежащих разным потокам делает Qt::QueuedConnection, и работает это только через цикл обработки сообщений.
для объектов принадлежащих одному потоку делает прямой вызов в обход цикла сообщений, выаполняется в контексте текущего потока (даже если это не тот самый поток).

для того, чтобы явно дернуть слот через цикл сообщений, проще всего вызвать QTimer::singleShot( 0, ... )
с 0 в качестве задержки он просто ставит в очередь событие "вызвать нужный слот" и возвращается.
Re[2]: Странное поведение объекта на QGraphicsScene
От: XOOIOOX  
Дата: 09.05.20 08:47
Оценка:
Здравствуйте, K13, Вы писали:

K13>а коннект какой был? по умолчанию -- авто, который для объектов принадлежащих разным потокам делает Qt::QueuedConnection, и работает это только через цикл обработки сообщений.


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

Не совсем понятна механика данного действа. Недозаконченная очередь сообщений осталась? Объект можно уничтожать только в какой-то определенный момент? Уничтожение извне работало при этом нормально. Тот же самый сигнал по таймеру внутри объекта, опять же, нормально отрабатывал. Или дело в том, что нужно было выйти из метода advance?
Re[3]: Странное поведение объекта на QGraphicsScene
От: K13 http://akvis.com
Дата: 10.05.20 17:53
Оценка:
Здравствуйте, XOOIOOX, Вы писали:

XOO>Не совсем понятна механика данного действа. Недозаконченная очередь сообщений осталась? Объект можно уничтожать только в какой-то определенный момент? Уничтожение извне работало при этом нормально. Тот же самый сигнал по таймеру внутри объекта, опять же, нормально отрабатывал. Или дело в том, что нужно было выйти из метода advance?


Нельзя удалять объект изнутри его слота какого-то события. Падает.
Можно сделать deleteLater() чтобы он удалился "чуть позже".
Re[4]: Странное поведение объекта на QGraphicsScene
От: XOOIOOX  
Дата: 10.05.20 18:38
Оценка:
Здравствуйте, K13, Вы писали:

K13>Нельзя удалять объект изнутри его слота какого-то события. Падает.


Получается, что так. Собственно, если нагло деструктор вызвать внутри объекта во время advance, падает еще более "жестко".

K13>Можно сделать deleteLater() чтобы он удалился "чуть позже".


Пробовал. Не помогало. Событие, видимо, не до конца отрабатывалось.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.