QTimer::SingleShot fired after object is deleted
Asked Answered
W

5

6
// Example class
class A : public QObject
{
   Q_OBJECT
   void fun() {
       Timer::SingleShot(10, timerSlot); //rough code
   }
   public slot:
   void timerSlot();
}

auto a = SharedPointer<A>(new A);
a->fun();
a->reset(); // a deleted

In this case after a is deleted and timer is fired, would it execute timerSlot()? I'm getting an extremely rare crash and not sure if it's because of something fishy in this logic.

Womanly answered 17/6, 2016 at 1:29 Comment(1)
Please remove "rough code" and add the exact code in there. In a testcase the devil is in the details...Rosinweed
Y
9

Even if the timer fires, it won't trigger the slot. The docs of ~QObject state: All signals to and from the object are automatically disconnected, and any pending posted events for the object are removed from the event queue. The only way you can trigger the A::timerSlot and delete A at the same time is if you use threads.

Yam answered 17/6, 2016 at 8:1 Comment(3)
When OP posted the question, QObject was not there. Now it is. I like this answer given the new requirements.Silkweed
You write "The only way you can trigger the A::timerSlotand delete A at the same time is if you use threads.". Can you please demonstrate that? What do you mean by "at the same time"? Do you mean if he exploits data races?Chiles
@JohannesSchaub-litb Exactly. It's bad design and shouldn't be done of course, but it's a possibility.Yam
S
2

You are not obligated to disconnect an object's signals and slots before deleting it.

The QObject destructor will clean up obsolete signal-slot connection for you, as long as you:

  1. Inherit from QObject

  2. Use the Q_OBJECT macro in your class definition

Following these conventions ensures that your object emits a destroyed() signal when deleted. That's actually what Qt's signals-and-slots system uses to clean up dangling references.

You can listen to the destroyed() signal yourself if you'd like to add some debugging code to track object lifecycles.

(Depending on the particular version of Qt/moc you are using, it's quite possible that code with a non-QObject using slots, or a QObject-derived class that doesn't have Q_OBJECT in its header will still compile but cause the timerSlot() method to be invoked on a garbage pointer at runtime.)

Southerly answered 17/6, 2016 at 2:1 Comment(1)
Yes it is inherited from QObjectWomanly
S
0

Edit: This answer was in response to the original question which did not use QObject but had class A as a standalone class inheriting nothing. The question was later edited making this answer obsolete, but I'll leave it here to show what would be needed if not using QObject.


The only way you can do that is if you keep the object alive until the timer has fired. For example:

class A : enable_shared_from_this<A> {
   void fun() {
       QTimer::singleShot(10, bind(&A::timerSlot, shared_from_this()));
   }
public:
   void timerSlot();
}

auto a = SharedPointer<A>(new A);
a->fun();
a->reset(); // a goes out of scope, but its referent is kept alive by the `QTimer`.

The reason the above works is that you capture a shared_ptr to class A when setting the timer, and the timer will hold onto it (else it can't fire).

If you don't like or can't use recent C++ features or Boost:

struct Functor {
    Functor(SharedPointer<A> a) : _a(a) {}
    void operator() { a->timerSlot(); }
    SharedPointer _a;
};

class A {
   void fun(shared_ptr<A> self) {
       QTimer::singleShot(10, Functor(self));
   }
public:
   void timerSlot();
}

auto a = SharedPointer<A>(new A);
a->fun(a);
Silkweed answered 17/6, 2016 at 1:34 Comment(4)
So, main question, would the timer fire (as per my code base) if a goes out of scope? or QTimer would be invalidated?Womanly
The timer would fire unless you destroy or disable the timer. And when it fires, in your original code, undefined behavior would result, because the this pointer would be invalid (use-after-free).Silkweed
Just a note: your "without recent C++ features" section uses std::shared_ptr, which was introduced in C++11.Edina
@JonHarper: Thanks. It was a simple typo. Fixed now (using SharedPtr which OP has).Silkweed
P
0

I'm getting a extremely rare crash due to timer out of object scope which I need to fire just once. I use QTimer::singleShot which is static method and does not pertain to an instance of QTimer object which I would release with the context it fires the signal to.

That is of course solved in QTimer class and desired behavior controlled by the instance of timer class with non-static QTimer::singleShot property set to true.

// declaration
   QScopedPointer<QTimer> m_timer;

protected slots:
   void onTimeout();

// usage
m_timer.reset(new QTimer);
m_timer->setSingleShot(true);
QObject::connect(m_timer.data(), SIGNAL(timeout()), this, SLOT(onTimeout()));
m_timer->start(requiredTimeout);

So, no crash should happen due to timer released with the context object.

Pandarus answered 17/6, 2016 at 3:34 Comment(0)
E
-1

To reach certainty, you can stop the timer yourself:

class A : public QObject {
    QTimer t;
    A()  { connect(Signal-Slots); }
    ~A() { t.stop(); }
    fun() { t.start(10); }
    ...
};
Except answered 17/6, 2016 at 11:58 Comment(3)
Thats not what I askedWomanly
@NullPointer. if You implement it this way, Your question is pointless!Except
@NullPointer: your problem report contains not only a question, but also a crash report, which is the most disqualifying result in programming art. Therefor I assumed, that your prominent goal is to avoid your program crashing, and offered you a program design, which avoid the suspect crash cause. Compare my with the proposal of Alexander and make most certain synthesis.Except

© 2022 - 2024 — McMap. All rights reserved.