使用 QGraphicsEffect 删除 QGraphicsItem 会导致段错误

Deleting QGraphicsItem with QGraphicsEffect leads to segfault

以下程序绘制红色背景。按下鼠标左键会在其上绘制一个白色矩形。该矩形有一个子矩形和一个 QGraphicsDropShadowEffect。 (QGraphicsOpacityEffectQGraphicsColorizeEffect 也会导致问题,但频率较低。)

按鼠标右键删除白色矩形。

有时删除矩形会导致分段错误。

如果不应用 QGraphicsDropShadowEffect,则不会发生这种情况。当 MyRect 中没有子项目或背景被省略时也不会发生。

(在搜索这个问题时,我发现了一些提示,表明像这样的段错误可能与在不调用 prepareGeometryChange() 的情况下更改项目的 boundingRect() 有关。)

这里真的是一头雾水。这是一个更大项目的一部分,我将其归结为以下示例:

Main.cc:

#include<QApplication>
#include<QGraphicsView>
#include<QGraphicsScene>
#include<QGraphicsSceneMouseEvent>
#include<QGraphicsRectItem>
#include<QGraphicsDropShadowEffect>
#include<QScreen>

class MyRect: public QGraphicsRectItem
{
    public:
    MyRect(QGraphicsItem* parent = nullptr):
        QGraphicsRectItem{QRectF{0.0f, 0.0f, 100.0f, 100.0f}, parent}
    {
        setPen(QPen{Qt::white});
        setBrush(QBrush{Qt::white, Qt::SolidPattern});
        my_child_=new QGraphicsRectItem{this};
    }

    private:
    QGraphicsRectItem* my_child_=nullptr;
};

class MyScene: public QGraphicsScene
{
    public:
    MyScene()
    {
        background_=new QGraphicsRectItem{0.0f, 0.0f, 500.0f, 500.0f};
        background_->setPen(QPen{Qt::white});
        background_->setBrush(QBrush{Qt::red, Qt::SolidPattern});
        addItem(background_);
    }

    void mouseReleaseEvent(QGraphicsSceneMouseEvent* me) override
    {
        if (me->button()==Qt::LeftButton)
        {
            if (my_rect_==nullptr)
            {
                my_rect_=new MyRect{};
                shadow_=new QGraphicsDropShadowEffect{};
                shadow_->setBlurRadius(15.0f);
                my_rect_->setGraphicsEffect(shadow_);
                addItem(my_rect_);
                my_rect_->setPos(me->scenePos());
            }
        }
        else if (me->button()==Qt::RightButton)
        {
            if (my_rect_!=nullptr)
            {
                removeItem(my_rect_);
                my_rect_->setGraphicsEffect(0);
                shadow_=nullptr;
                delete my_rect_;
                my_rect_=nullptr;
            }
        }
        else
        {
            QGraphicsScene::mouseReleaseEvent(me);
        }
    }

    private:
    QGraphicsRectItem* background_=nullptr;
    MyRect* my_rect_=nullptr;
    QGraphicsDropShadowEffect* shadow_=nullptr;
};

int
main(int argc, char** argv)
{
    QApplication qapp{argc, argv};

    QGraphicsView view;
    MyScene scene;

    QRect rect=QGuiApplication::primaryScreen()->geometry();
    scene.setSceneRect(0.0f, 0.0f, rect.width(), rect.height());
    view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    view.setFrameShape(QFrame::NoFrame);
    view.setBackgroundBrush(QBrush(Qt::black, Qt::SolidPattern));
    view.setScene(&scene);
    view.showFullScreen();

    return qapp.exec();
}

编译器调用:

g++ --std=c++14 -fPIC -Wall -Woverloaded-virtual -Werror -pedantic -g -O0 -fPIC -I/usr/include/x86_64-linux-gnu/qt5/QtGui -I/usr/include/x86_64-linux-gnu/qt5/QtWidgets -I/usr/include/x86_64-linux-gnu/qt5/QtCore -I/usr/include/x86_64-linux-gnu/qt5 -o test Main.cc -lQt5Gui -lQt5Core -lQt5Widgets

回溯:

#0  0x0000555555a9a840 in ?? ()
#1  0x00007ffff71e017c in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#2  0x00007ffff71e0a3a in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#3  0x00007ffff720289a in QGraphicsView::paintEvent(QPaintEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#4  0x00007ffff6f10278 in QWidget::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#5  0x00007ffff6ff89fe in QFrame::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#6  0x00007ffff72013ab in QGraphicsView::viewportEvent(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#7  0x00007ffff7650701 in QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#8  0x00007ffff6ec8b65 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#9  0x00007ffff6ed0341 in QApplication::notify(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#10 0x00007ffff76509a0 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#11 0x00007ffff6f08fda in QWidgetPrivate::sendPaintEvent(QRegion const&) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#12 0x00007ffff6f09646 in QWidgetPrivate::drawWidget(QPaintDevice*, QRegion const&, QPoint const&, int, QPainter*, QWidgetBackingStore*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#13 0x00007ffff6ed8f1e in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#14 0x00007ffff6ed9147 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#15 0x00007ffff6ef7f8f in QWidgetPrivate::syncBackingStore() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#16 0x00007ffff6f10348 in QWidget::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#17 0x00007ffff6ff89fe in QFrame::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#18 0x00007ffff7081de3 in QAbstractScrollArea::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#19 0x00007ffff6ec8b8c in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#20 0x00007ffff6ed0341 in QApplication::notify(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#21 0x00007ffff76509a0 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#22 0x00007ffff765312d in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#23 0x00007ffff71d2a22 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#24 0x00007ffff71d8299 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#25 0x00007ffff767d459 in QObject::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#26 0x00007ffff71e4e6b in QGraphicsScene::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#27 0x00007ffff6ec8b8c in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#28 0x00007ffff6ed0341 in QApplication::notify(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#29 0x00007ffff76509a0 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#30 0x00007ffff765312d in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#31 0x00007ffff76a4c03 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#32 0x00007ffff446f7f7 in g_main_context_dispatch () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#33 0x00007ffff446fa60 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#34 0x00007ffff446fb0c in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#35 0x00007ffff76a500f in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#36 0x00007ffff764e98a in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#37 0x00007ffff76570fc in QCoreApplication::exec() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#38 0x000055555555768b in main (argc=1, argv=0x7fffffffe5b8) at Main.cc:89

编辑:我重写了代码示例以提高可读性。

因为我接受过 on the Qt forums 的教育,这不是预期的行为,可能是 Qt 中的错误。

最好的解决方法是在 my_rect_ 上调用 QGraphicsItem::prepareGeometryChange(),然后 将其从场景中移除。这可以通过公开受保护的 prepareGeometryChange() 或在 ~MyRect() 中调用它然后简单地删除 my_rect_ 而无需 调用 QGraphicsScene::removeItem() 来完成。 ~QGraphicsItem(),当然是在~MyRect()之后调用的,会自动从场景中移除物品。