简体   繁体   中英

List of subclass pointers that all derive from the same superclass (C++)

Take this example code:

#include <QWidget>
#include <QMainWindow>
#include <QList>
#include <QGridLayout>
#include <QLabel>
#include <QDialogButtonBox>
#include <QApplication>

class MainWindow;

class BaseWidget : public QWidget
{
public:
    BaseWidget() = default;
    virtual ~BaseWidget() {};

    virtual void display(MainWindow *window) {}
};

class MainWindow : public QMainWindow
{
private:
    QList<BaseWidget *> queue;
public:
    void setWidget(BaseWidget *widget) {
        if (queue.isEmpty() || queue.last() != widget) {
            queue += widget;
        }
        this->setCentralWidget(widget);
    }
public slots:
    void back() {
        queue.removeLast();
        this->setWidget(queue.last());
    }
};

class DerivedWidget2 : public BaseWidget
{
public:
    void display(MainWindow *window) {
        QGridLayout *layout = new QGridLayout(this);
        QLabel *label = new QLabel(tr("hit \"Ok\" to segfault"));
        layout->addWidget(label);

        QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok);
        connect(box, &QDialogButtonBox::accepted, window, &MainWindow::back);
        layout->addWidget(box);

        window->setWidget(this);
    }
};

class DerivedWidget : public BaseWidget
{
public:
    void display(MainWindow *window) {
        QGridLayout *layout = new QGridLayout(this);
        QLabel *label = new QLabel(tr("hit \"Ok\" to move to the next"));
        layout->addWidget(label);

        QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok);
        connect(box, &QDialogButtonBox::accepted, window, [window] {
            DerivedWidget2 *widget2 = new DerivedWidget2;
            widget2->display(window);
        });
        layout->addWidget(box);

        window->setWidget(this);
        window->show();
    }
};

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

    MainWindow *window = new MainWindow;
    DerivedWidget *widget = new DerivedWidget;

    widget->display(window);
    return app.exec();
}

(Note that I've got a lot more derived widgets, so no I'm not gonna put a bunch of ifs and dynamic_cast s. Also, all of this stuff would be in completely separate scopes/files.)

What I'm doing is storing these derived widgets in a "queue", where the last one is the currently displayed one, and you can go "back" in the queue, which will set the MainWindow 's central widget (ie, which widget it displays to the user) to the previous widget in the queue.

So, everything seems to work fine, except one thing. When the widget is being displayed, in the queue list it stays as the derived class (according to my debugger). However, when I change the displayed widget, any widgets in the queue not currently being displayed seem to "degrade" into the base class (or at least that's what my debugger says), and it for some reason loses every single one of its properties. So, when I try to do something with that widget (like going back in the queue, back to that widget, and setting the main window to display it), it just segfaults.

Oddly enough, however, when I look in my debugger, there are other instances of that exact same widget pointer, pointing to the exact same memory address - they're just of the subclass type, while in the list it's "degraded" to the superclass.

Why is this happening? Anything I can do to fix it?

EDIT : Here's a video showing this with the debugger, should hopefully make it more clear.

EDIT 2 : When the BaseWidget class has actual members, when this segfault happens those members seem to all completely deallocate and "die", or turn into complete garbage in certain cases.

As someoneinthebox commented, Qt doc about QMainWindow::setCentralWidget says:

Note: QMainWindow takes ownership of the widget pointer and deletes it at the appropriate time.

So, second time you do this->setCentralWidget(widget); , the widget that was previously used as central widget gets automatically deleted by Qt (See Memory management in Qt? ). So when you try to go back, you set as central widget a deleted widget, so you obviously get a segmentation fault. You can observe that by setting a breakpoint in BaseWidget destructor, you'll hit the breakpoint on second call to this->setCentralWidget(widget); .

So you need to have permanent widget as central widget and attach the BaseWidget derived class items as child of it (and prevent them from being automatically deleted by Qt). I propose to use QStackedLayout which allows to store many widgets in a layout and have only one be visible (but there could be other working strategies):

Your MainWidget class would then be:

class MainWindow : public QMainWindow
{
private:
    QList<BaseWidget *> queue;
    QWidget* centralWidget;
    QStackedLayout* stackedLayout;
    
public:
    MainWindow()
    {
        this->setCentralWidget(centralWidget = new QWidget());
        centralWidget->setLayout( stackedLayout = new QStackedLayout() );
        stackedLayout->setContentsMargins(0,0,0,0);
    }

    void setWidget(BaseWidget *widget) {
        if (queue.isEmpty() || queue.last() != widget) {
            queue += widget;
        }

        // old code (segfault:
        // this->setCentralWidget(widget);

        // new code:
        if ( stackedLayout->indexOf( widget ) == -1 )
            stackedLayout->addWidget( widget );
        // widget already in stackedLayout (not the first time it is being shown)

        stackedLayout->setCurrentWidget( widget );
    }
public slots:
    void back() {
        queue.removeLast();
        this->setWidget(queue.last());
    }
};

Then it does not crash anymore.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM