0

In Qt I have to wait for 1 second and let event process so I use

QTime lDieTime= QTime::currentTime().addSecs(1);
while (QTime::currentTime() < lDieTime)
    QCoreApplication::processEvents(QEventLoop::AllEvents, 100);

But after this while, I do operation on the object ( this->mListIP.currentItem();)

This work fine if the object has not been destroyed during the processEvents. But the object can be destroyed during this loop.

How can I test that the widget/object still exist?

I have tried if(!this->isVisible()) but it crash (read access violation) because this does not exist anymore

My problem is related to this question

So if I can't check if this exist, how can I stop my function if the object is destroyed? Something in the destructor? A try-catch (but I dont like that)

Note : the function is called by a timerEvent from a QBasicTimer every 2 seconds

Edit : structure of the code :

Class A (Widget)
   -constructor start a QBasicTimer of 2 seconds
   -timerEvent, called by the QBasicTimer every two seconds
Layout containing Class A can destroy Class A object

Inside the timerevent, i start an operation, wait for 1 seconds, then try to update the widget. But I cant update it if it's been destroyed

edit2 : more code

void MaryAnnConnectionWidget::timerEvent( QTimerEvent * /*aEvent*/ )
{
    //operation base on a socket and a udp broadcast
    ...

    QTime lDieTime= QTime::currentTime().addSecs(1);
    while (QTime::currentTime() < lDieTime)
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);

    //Operation to handle answers from the broadcast
    ...

    if(this==nullptr) //Exit if we changed window <= CRASH HERE if widget destroyed
        return;

    //Handle the answers and displays results
    ...

}
Community
  • 1
  • 1
David Levy
  • 430
  • 5
  • 18
  • 1
    Don't use `QCoreApplication::processEvents(QEventLoop::AllEvents, 100);`. It is a dirty pattern. If you need any asynchronous operations - use timers/multithreading/concurrency. – Dmitry Sazonov Nov 22 '16 at 22:43
  • Btw, don't mix a network code with gui code. Split GUI and network logic to separated classes and connect them via signals/slots. – Dmitry Sazonov Nov 22 '16 at 22:44
  • We wanted to avoid another thread if possible, thats why im struggling with that. And my network code is in an external library (that we code aswell). Its two static functions `SOCKET SendRequest(char* ip)` and `ReadAnswer(SOCKET)`. But thats right, I could do something cleaner with signal & slots. I'll try tomorrow – David Levy Nov 22 '16 at 22:54

2 Answers2

3

Your question is a sort of an XY problem. You thought your problem was about how to track object's lifetime. One solution is to use a QPointer to do that. Another is not to have to explicitly track it and let the framework ensure that your code doesn't run on objects that aren't there anymore. So the solutions are complementary, but it's best to avoid the problem in the first place. Below I'll explain how to do so.

Suppose that you start with the following horrible code. You must factor out doBefore() and doAfter() into stand-alone methods:

void Class::timerEvent(QTimerEvent * ev) {
  if (ev->timerId() != m_timer.timerId()) return;
  doBefore();
  auto endTime = QTime::currentTime().addSecs(1);
  while (QTime::currentTime() < endTime)
    QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
  doAfter(mListIP.currentItem());
}

Then it becomes easy to address your concerns by performing two transformations on the code.

First Transformation

In Qt I have to wait for 1 second and let event process so I use

No, you don't. You have to run your code after one second has elapsed. Here's how:

// Qt 5

void Class::timerEvent(QTimerEvent * ev) {
  if (ev->timerId() != m_timer.timerId()) return;
  doBefore();
  QTimer::singleShot(1000, this, [this]{ doAfter(mListIP.currentItem()); });
}

// Qt 4

void Class::timerEvent(QTimerEvent * ev) {
  if (ev->timerId() != m_timer.timerId()) return;
  doBefore();
  m_currentItem = mListIP.currentItem()
  QTimer::singleShot(1000, this, SLOT(doAfterCurrent()));
}
void Class::doAfterCurrent() { // must be a slot
  doAfter(m_currentItem);
}

Second Transformation

I do operation on the object ( this->mListIP.currentItem();)

If you run the functor/slot in that object's context, the functor/slot won't execute if the object dies. Thus:

// Qt 5

void Class::timerEvent(QTimerEvent * ev) {
  if (ev->timerId() != m_timer.timerId()) return;
  doBefore();
  auto item = mListIP.currentItem();
  Q_ASSERT(!item || this.findChildren<QObject*>().contains(item));
  //                       vvvv -- note the changed context!
  QTimer::singleShot(1000, item, [this,item]{ doAfter(item); });
}

// Qt 4

void Class::timerEvent(QTimerEvent * ev) {
  if (ev->timerId() != m_timer.timerId()) return;
  doBefore();
  m_currentItem = mListIP.currentItem();
  Q_ASSERT(!m_currentItem || this.findChildren<QObject*>().contains(m_currentItem));
  //                       vvvvvvvvvvvv -- note the changed context!
  QTimer::singleShot(1000, m_curentItem, SLOT(doAfterCurrent()));
}
void Class::doAfterCurrent() { // must be a slot
  doAfter(m_currentItem);
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • It is indeed a way better design. I accepted the other answer because its more directly related to my question title. But I will use this design. so +1 – David Levy Nov 23 '16 at 15:16
  • It works, thanks. I just simplified it a bit because `this` and mListIP have the same lifetime – David Levy Nov 23 '16 at 18:53
1

If you need a safe pointer to a QObject, use QPointer. It will become null when the object is destroyed. It isn't clear how your code is constructed and where you're trying to do this, but you can also connect to the object's "destroyed" signal, and tie that to a slot that aborts the timer so that it doesn't attempt to reference the object.

goug
  • 2,294
  • 1
  • 11
  • 15
  • Updated a bit my question to detail the structure of my code. I''l try the QPointer and keep you posted – David Levy Nov 22 '16 at 19:09
  • That helps. Since class A owns the timer, then at least stop the timer in class A's destructor. That's much more simple than pointer games. I'm not quite sure how your timer code is written that it isn't getting destroyed automatically. Are you allocating a pointer to a timer and then not destroying it? If so, that would be a memory leak. – goug Nov 22 '16 at 19:41
  • I've added the code of my timerevent. I dont think i have to stop the timer, Qt does it at destruction. The issue is that the class A object is destroyed during the timerevent execution – David Levy Nov 22 '16 at 21:10
  • In that case, I think you need to use a QPointer to "this". It's maybe a little odd to use it inside the class it refers to, but I think that'll work. Make the QPointer a member object, initialize it in your constructor, and then in place of "if (this == null)", check the value of the QPointer member object. – goug Nov 22 '16 at 22:54