您正在尝试手动管理内存。如您所见,使用悬空指针或犯其他错误非常容易。相反,让编译器为您完成。
你错误地使用了static。
如果我要这样做,我会这样做。析构函数由编译器生成,会正确释放所有资源并将m_instance重置为空值。
class MyWindow : public QDialog
{
Q_OBJECT
static QPointer<MyWindow> m_instance;
QVBoxLayout m_layout{this};
QPushButton m_button{"Scan"};
public:
MyWindow(QWidget * parent = nullptr) : QDialog(parent) {
Q_ASSERT(! m_instance);
m_instance = this;
m_layout.addWidget(&m_button);
}
void Scan();
static void ScanFinished();
};
QPointer<MyWindow> MyWindow::m_instance;
void StartScan(void(*callback)());
void MyWindow::Scan()
{
m_button.setEnabled(false);
StartScan(ScanFinished);
}
void MyWindow::ScanFinished()
{
m_instance->m_button.setEnabled(true);
}
在这一点上很明显StartScan 的API 被严重破坏了,这种破坏迫使使用单例MyWindow。在执行任何类型的回调时,您从不使用单独的 C 函数指针。您必须同时接受带有void* 和void* 的函数指针,该指针将用于携带函数工作所需的数据。这是一个成语。如果你使用 C 风格的回调,你不能在不严重削弱 API 可用性的情况下使用这种习惯用法。
因此,这是一个完整的示例,适用于 Qt 4 和 Qt 5。您应该在您的问题中发布类似的内容 - 一个独立的测试用例。它可以编译并且可以工作,您甚至可以从 github 获得完整的 Qt Creator 项目。它将在当前 Qt 支持的所有平台上编译和运行。这应该不难:毕竟这就是你使用 Qt 的原因。养成创建如此简洁的测试用例来展示您的问题的习惯将使您成为更好的开发人员,并使您的问题更容易回答。
// https://github.com/KubaO/stackoverflown/tree/master/questions/simple-callback-43094825
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#include <QtConcurrent>
#endif
class MyWindow: public QDialog
{
Q_OBJECT
QVBoxLayout m_layout{this};
QPushButton m_button{"Scan"};
Q_SIGNAL void ScanFinished();
public:
MyWindow(QWidget * parent = nullptr) : QDialog(parent) {
m_layout.addWidget(&m_button);
connect(&m_button, SIGNAL(clicked(bool)), this, SLOT(Scan()));
connect(this, SIGNAL(ScanFinished()), this, SLOT(OnScanFinished()));
}
Q_SLOT void Scan();
static void ScanFinishedCallback(void* w);
Q_SLOT void OnScanFinished();
};
void StartScan(void(*callback)(void*), void* data) {
// Mockup of the scanning process: invoke the callback after a delay from
// a worker thread.
QtConcurrent::run([=]{
struct Helper : QThread { using QThread::sleep; };
Helper::sleep(2);
callback(data);
});
}
void MyWindow::Scan()
{
m_button.setEnabled(false);
StartScan(ScanFinishedCallback, static_cast<void*>(this));
}
void MyWindow::ScanFinishedCallback(void* data)
{
emit static_cast<MyWindow*>(data)->ScanFinished();
}
void MyWindow::OnScanFinished()
{
m_button.setEnabled(true);
}
int main(int argc, char ** argv) {
QApplication app(argc, argv);
MyWindow w;
w.show();
return app.exec();
}
#include "main.moc"
当然StartScan 不能在调用它的线程中完成工作:它会阻塞 GUI 线程并使您的应用程序无响应。这是糟糕的用户体验的主要来源。相反,它应该生成一个并发作业,在扫描完成时通知调用者。
由于回调将从该并发线程中调用,因此使用MyWindow 的非线程安全方法是不安全的。唯一线程安全的方法是信号——因此我们可以发出一个信号,Qt 将安全地转发到MyWindow 的线程并从正确的线程调用OnScanFinished。