QTimer::SingleShot 在对象被删除后触发

Posted

技术标签:

【中文标题】QTimer::SingleShot 在对象被删除后触发【英文标题】:QTimer::SingleShot fired after object is deleted 【发布时间】:2016-10-18 17:05:58 【问题描述】:
// 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

在这种情况下,在删除 a 并触发计时器后,它会执行 timerSlot() 吗?我遇到了极其罕见的崩溃,不确定是不是因为这个逻辑有问题。

【问题讨论】:

请删除“粗略代码”并在其中添加精确代码。在测试用例中,魔鬼在细节中...... 【参考方案1】:

即使计时器触发,它也不会触发插槽。 ~QObject 状态的文档:All signals to and from the object are automatically disconnected, and any pending posted events for the object are removed from the event queue. 唯一可以同时触发A::timerSlot 和删除A 的方法是使用线程。

【讨论】:

当 OP 发布问题时,QObject 不存在。现在它是。考虑到新的要求,我喜欢这个答案。 您写“唯一可以同时触发 A::timerSlot 和删除 A 的方法是使用线程。”。你能证明一下吗? “同时”是什么意思?你的意思是他是否利用了数据竞赛? @JohannesSchaub-litb 没错。这是糟糕的设计,当然不应该这样做,但这是有可能的。【参考方案2】:

您没有义务在删除对象之前断开它的信号和插槽。

QObject 析构函数将为您清理过时的信号槽连接,只要您:

    从 QObject 继承

    Use the Q_OBJECT macro in your class definition

遵循这些约定可确保您的对象在删除时发出destroyed() 信号。这实际上就是 Qt 的信号槽系统用来清理悬空引用的方法。

如果您想添加一些调试代码来跟踪对象生命周期,您可以自己收听destroyed() 信号。

(根据您使用的 Qt/moc 的特定版本,使用插槽的非 QObject 的代码或在其标头中没有 Q_OBJECT 的 QObject 派生类的代码很可能仍然可以编译,但会导致timerSlot() 方法将在运行时在垃圾指针上调用。)

【讨论】:

是的,它继承自 QObject【参考方案3】:

由于计时器超出对象范围,我遇到了极其罕见的崩溃,我只需要触发一次。我使用 QTimer::singleShot,它是静态方法,与 QTimer 对象的实例无关,我将在它触发信号的上下文中释放它。

这当然可以在QTimer 类中解决,并且期望的行为由计时器类的实例控制,其中非静态QTimer::singleShot 属性设置为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);

因此,不会因为与上下文对象一起释放的计时器而发生崩溃。

【讨论】:

【参考方案4】:

编辑:此答案是对未使用 QObject 但将 class A 作为不继承任何内容的独立类的原始问题的回应。后来对该问题进行了编辑,使该答案已过时,但我将把它留在这里以说明如果不使用 QObject 会需要什么。


您可以做到这一点的唯一方法是让对象保持活动状态,直到计时器触发。例如:

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`.

上述工作的原因是您在设置计时器时将 shared_ptr 捕获到class A,并且计时器将保持它(否则它无法触发)。

如果您不喜欢或不能使用最新的 C++ 功能或 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);

【讨论】:

所以,主要问题是,如果a 超出范围,计时器会触发(根据我的代码库)吗?还是 QTimer 会失效? 除非您销毁或禁用计时器,否则计时器将触发。当它触发时,在您的原始代码中,将导致未定义的行为,因为 this 指针将无效(释放后使用)。 请注意:您的“没有最近的 C++ 功能”部分使用 std::shared_ptr,它是在 C++11 中引入的。 @JonHarper:谢谢。这是一个简单的错字。现已修复(使用 OP 拥有的 SharedPtr)。【参考方案5】:

为了确定,您可以自己停止计时器:

class A : public QObject 
    QTimer t;
    A()   connect(Signal-Slots); 
    ~A()  t.stop(); 
    fun()  t.start(10); 
    ...
;

【讨论】:

我问的不是这个 @NullPointer。如果你以这种方式实现它,你的问题是没有意义的! @NullPointer:你的问题报告不仅包含问题,还包含崩溃报告,这是编程艺术中最不合格的结果。因此我假设,您的突出目标是避免您的程序崩溃,并为您提供一个程序设计,避免可疑的崩溃原因。将我的建议与亚历山大的建议进行比较,并做出最确定的综合。

以上是关于QTimer::SingleShot 在对象被删除后触发的主要内容,如果未能解决你的问题,请参考以下文章

QTimer::singleShot()函数使用

QTimer::singleShot(0, object SLOT(obj_slot())) 做啥?

QTimer::singleShot 仅在间隔为 0 时调用 lambda

PyQT实时显示串口数据在QtCore.QTimer.singleShot()上抛出最大递归深度超出异常

当 singleShot 为 True 时,QTimer 永远不会触发超时 [关闭]

Qt中使用定时器(可使用QObject::timerEvent定时执行,QTimer::singleShot可只触发一次)