【问题标题】:QT Application segmentation fault on exit when using std::unique_ptr for the QMainWindow将 std::unique_ptr 用于 QMainWindow 时退出时出现 QT 应用程序分段错误
【发布时间】:2017-12-09 05:27:39
【问题描述】:

我应该首先澄清一下,我的问题是,为什么在我使用 std::unique_ptr 的情况下会发生段错误,但当我将代码更改为使用 new 和 delete 时却不会发生?我在这里真的没有问题,因为段错误已修复,并且我知道我正在使用 new 和 delete 正确清理内容。我只是想知道为什么我不能使用std::unique_ptr

如果我在std::unique_ptr 中创建我的QMainWindow 实例并且我的MainWindow 创建一个子窗口(例如打开QComboBoxToolTip),我的应用程序将在退出时出现段错误。在这种情况下,如果我的MainWindow 没有创建任何子窗口,则没有段错误。

如果我自己使用 new 和 delete 管理我的 QMainWindow 实例,则无论我是否创建子窗口都不会出现段错误。

这是我的主要功能,它只是在我的Bridge 类上调用run,该类处理创建我的MainWindow 类的实例并启动它。

int main(int argc, char *argv[])
{
    Bridge bridge(argc, argv);
    bridge.run(); // Handles starting the main window
}

这是我的 Bridge 类的缩短版本,它会导致段错误(使用 std::unique_ptr 我在退出时得到段错误):

class Bridge::IMPL {
public:
    IMPL(int& argc, char ** argv) : 
        mainwindow(), isRunning(true), app(argc, argv) {}
    ~IMPL() = default;
public:
    std::unique_ptr<MainWindow> mainwindow;
    bool isRunning;
    QApplication app;
};

Bridge::Bridge(int& argc, char ** argv) :
    pImpl(make_unique<IMPL>(argc, argv)) {

    pImpl->mainwindow.reset(new MainWindow(this));
}

这是没有段错误的 Bridge 类(稍作修改以删除 std::unique_ptr 并使用 new 和 delete。此版本的 Bridge 类不会崩溃):

class Bridge::IMPL {
public:
    IMPL(int& argc, char ** argv) : 
        mainwindow(nullptr), isRunning(true), app(argc, argv) {}
    ~IMPL() {
        if (mainwindow) {
            delete mainwindow;
        }
    }
public:
    MainWindow* mainwindow;
    bool isRunning;
    QApplication app;
};

Bridge::Bridge(int& argc, char ** argv) :
    pImpl(make_unique<IMPL>(argc, argv)) {

    pImpl->mainwindow= new MainWindow(this);
}

这是发生段错误时的回溯:

还有一个关于 QBasicTimer 的奇怪打印输出在我将代码更改为使用 new 和 delete 而不是 std::unique_ptr 后没有打印出来

QBasicTimer::start: QBasicTimer can only be used with threads started with QThread

Thread 1 "application" received signal SIGSEGV, Segmentation fault.
0x00007fffe75bf2e2 in ?? () from /home/user/Qt/5.9/gcc_64/plugins/platforms/../../lib/libQt5XcbQpa.so.5
(gdb) bt
#0  0x00007fffe75bf2e2 in ?? () from /home/user/Qt/5.9/gcc_64/plugins/platforms/../../lib/libQt5XcbQpa.so.5
#1  0x00007fffe75bf5c4 in ?? () from /home/user/Qt/5.9/gcc_64/plugins/platforms/../../lib/libQt5XcbQpa.so.5
#2  0x00007fffe75b9669 in QXcbConnection::removeWindowEventListener(unsigned int) () from /home/user/Qt/5.9/gcc_64/plugins/platforms/../../lib/libQt5XcbQpa.so.5
#3  0x00007fffe75ceafa in QXcbWindow::destroy() () from /home/user/Qt/5.9/gcc_64/plugins/platforms/../../lib/libQt5XcbQpa.so.5
#4  0x00007fffe75cec07 in QXcbWindow::~QXcbWindow() () from /home/user/Qt/5.9/gcc_64/plugins/platforms/../../lib/libQt5XcbQpa.so.5
#5  0x00007fffe43ce2ee in ?? () from /home/user/Qt/5.9/gcc_64/plugins/xcbglintegrations/libqxcb-glx-integration.so
#6  0x00007ffff4b56f46 in QWindowPrivate::destroy() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Gui.so.5
#7  0x00007ffff534ecd7 in QWidgetPrivate::deleteTLSysExtra() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#8  0x00007ffff53524d8 in QWidget::destroy(bool, bool) () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#9  0x00007ffff53598b0 in QWidget::~QWidget() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#10 0x00007ffff541ae7a in ?? () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#11 0x00007ffff4580b83 in QObjectPrivate::deleteChildren() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Core.so.5
#12 0x00007ffff5359894 in QWidget::~QWidget() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#13 0x00007ffff540eac9 in QComboBox::~QComboBox() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#14 0x00007ffff4580b83 in QObjectPrivate::deleteChildren() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Core.so.5
#15 0x00007ffff5359894 in QWidget::~QWidget() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#16 0x00007ffff5359ab9 in QWidget::~QWidget() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#17 0x00007ffff4580b83 in QObjectPrivate::deleteChildren() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Core.so.5
#18 0x00007ffff5359894 in QWidget::~QWidget() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#19 0x00007ffff5359ab9 in QWidget::~QWidget() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#20 0x00007ffff4580b83 in QObjectPrivate::deleteChildren() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Core.so.5
#21 0x00007ffff5359894 in QWidget::~QWidget() () from /home/user/Qt/5.9/gcc_64/lib/libQt5Widgets.so.5
#22 0x0000000000421ed4 in MainWindow::~MainWindow() ()
#23 0x0000000000421f0e in MainWindow::~MainWindow() ()
#24 0x0000000000421698 in std::default_delete<MainWindow>::operator()(MainWindow*) const ()
#25 0x0000000000421171 in std::unique_ptr<MainWindow, std::default_delete<MainWindow> >::~unique_ptr() ()
#26 0x000000000042191c in Bridge::IMPL::~IMPL() ()
#27 0x0000000000421942 in std::default_delete<Bridge::IMPL>::operator()(Bridge::IMPL*) const ()
#28 0x0000000000421387 in std::unique_ptr<Bridge::IMPL, std::default_delete<Bridge::IMPL> >::~unique_ptr() ()
#29 0x0000000000420cb0 in Bridge::~Bridge() ()
#30 0x0000000000421c41 in main ()

还应注意,此QApplication 使用while 循环运行并调用app.processEvents(),而不是调用exec()。我意识到这不是最好的方法,但是这个应用程序是另一个应用程序的一部分,它从其他地方轮询事件,并且由于这个应用程序的性质比其他任何东西都更“概念验证”,我想保留通过将所有内容集中在一个线程中,这很简单。请参阅下面的运行方法的 sn-p:

while (isRunning()) {
    poller.poll(25);
    app.processEvents();
}

编辑:

在我的MainWindow 构造函数中,我传递的this 没有被设置为父级(父级是nullptr):

标题:

class MainWindow: public QMainWindow
{
    Q_OBJECT
public:
    class BridgeInterface{
    public:
        // Some pure virtual methods here
    };

    explicit MainWindow(BridgeInterface* interface, QWidget* parent = nullptr);

// some more stuff...

private:
    class IMPL;
    std::unique_ptr<IMPL> pImpl;
};

来源:

MainWindow::MainWindow(BridgeInterface* interface, QWidget* parent) :
    QMainWindow(parent), pImpl(make_unique<IMPL>()) {

    pImpl->ui->setupUi(this);
    pImpl->bridgeInterface = interface;

    // Connect slots and stuff
}

【问题讨论】:

  • pImpl(make_unique&lt;IMPL&gt;(argc, argv)) 看起来很可疑。 IMPL 确实会尝试删除指针吗?
  • 析构函数实现?它有什么作用?这些子小部件是如何实例化的?
  • pImpl 在Bridge 标头中定义为std::unique_ptr&lt;IMPL&gt;。来自 C++ 文档:std::make_unique“构造 T 类型的对象并将其包装在 std::unique_ptr 中。”
  • 子小部件为我处理。我在 QT creator 中设计了 MainWindow 并添加了一个QComboBox。我有一个 CMake 文件,我在其中执行此操作:QT5_WRAP_CPP(mainwindow_moc ${CMAKE_SOURCE_DIR}/include/MainWindow.hh)QT5_WRAP_UI(mainwindow_ui ${CMAKE_SOURCE_DIR}/include/MainWindow.ui)
  • 查看编辑。 delete mainwindow 也来自没有段错误的代码。段错误的代码是我使用 unqiue_ptr 的代码,因此我不会自己删除它。

标签: c++ qt c++11 segmentation-fault c++14


【解决方案1】:

很难确定,因为您没有发布 MainWindow 的代码,但我会说您要删除 mainwindow 两次。

pImpl-&gt;mainwindow= new MainWindow(this); 如果this 是父级,它会在销毁时自动删除 MainWindow,同样的事情也会做 unique_ptr

要么使用原始指针并依赖 Qt 的父子清理,要么使用智能指针而不将父级传递给 MainWindow

【讨论】:

  • 很抱歉。是的,在这种情况下,this 不是父母。我将发布问题的更新。此外,您引用的代码是没有段错误的版本。段错误的版本是我使用std::unique_ptr 的版本。
  • 我用我的MainWindow 头文件和源文件的 sn-p 更新了这个问题。我希望这会有所帮助。我的MainWindow 类的父级是nullptr
  • 您可以尝试将std::unique_ptr 替换为QScopedPointer&lt;MainWindow,QScopedPointerDeleteLater&gt;,看看它是否仍然存在段错误?
  • @IlBeldus QScopedPointerDeleteLater 如果在事件循环停止后调用它会导致内存泄漏。
【解决方案2】:

删除QApplication 实例后不要删除MainWindow。您需要以某种方式管理初始化和销毁​​顺序。

QApp::ctor Window::ctor Window::dtor QApp::dtor

可能的解决方案:直接在Bridge::~Bridge内部销毁MainWindow

附:你不需要通过引用传递int argc

附言关于 processEvents() - 您可以创建一个单独的线程并在那里运行您的 Qt 部分。如果你知道你在做什么,可以不从主线程使用 GUI,ofc :)。主要规则 - QApplication 实例和所有 GUI 应该从一个线程创建

【讨论】:

  • 所以解决方法就是将app(argc, argv)移到初始化列表的开头,非常好。在 PS 上,我不能 100% 确定你可以,来自doc.qt.io/qt-5/qapplication.html#QApplication:“argc 和 argv 引用的数据必须在 QApplication 对象的整个生命周期内保持有效。”如果您按值传递 argc 并被删除,则可能会很麻烦
  • @Dmitry QApplication 引用 argc QApplication(int &amp;argc, char **argv)
  • @StephenPape 没关系 :)
【解决方案3】:

在您的课程中,QApplication 的析构函数在您的主窗口被销毁之前被调用。这是因为类变量按照它们列出的顺序初始化,然后以相反的顺序销毁。

如果您移动QApplication app;,使其成为类中的第一个变量,则其析构函数将在主窗口被销毁后最后调用,这是正确的行为。

当你明确删除它时它起作用的原因是你正在通过 IMPL 的析构函数删除mainwindow。 ~IMPL() 在类变量析构函数运行之前运行,导致主窗口首先被销毁。

【讨论】:

  • 这解决了我的问题。谢谢。
猜你喜欢
  • 2017-04-14
  • 1970-01-01
  • 2012-08-14
  • 1970-01-01
  • 2020-08-18
  • 1970-01-01
  • 2021-03-26
  • 2017-02-21
  • 1970-01-01
相关资源
最近更新 更多