堆栈与堆属性的 QT 特定差异?

Ice*_*ire 1 c++ qt memory-management

通常,在编写 C++ 代码时,我会将对象始终作为普通属性保存,从而利用 RAII。然而,在 QT 中,删除对象的责任可以在QObject. 所以,假设我们定义了一些特定的小部件,那么我们有两种可能性:

1)使用QT的系统

class Widget1 : QWidget
{
Q_OBJECT
public:
    Widget1(QWidget* parent = nullptr);

private:
    QPushButton* myButton; // create it with "new QPushButton(this);"
};
Run Code Online (Sandbox Code Playgroud)

2)使用RAII

class Widget2 : public QWidget
{
Q_OBJECT
public:
    Widget2(QWidget* parent = nullptr);

private:
    QPushButton button; // normal RAII
};
Run Code Online (Sandbox Code Playgroud)

通常,我使用第一种方法。如果父母不仅通过布局了解其孩子,似乎有些事情会更好。但是仔细想想……真正的原因我还不是很清楚。

我知道堆栈是有限的。但是,假设这在这里不起作用。毕竟,堆栈不是那么小。

Mik*_*ike 5

如果父母不仅通过布局了解其孩子,似乎有些事情会更好。

你是对的。AQObject的父级不仅用于内存管理目的,这个答案总结了它的一些其他用法。这里最重要的是QWidget's的那些(因为您关心添加成员QWidget的),所以如果您按照编写它的方式使用第二种方法,以下是您可能会遇到的一些问题:

  • 假设您正在Widget1像这样在主函数中实例化和显示它:

    Widget1 w;
    w.show();
    
    Run Code Online (Sandbox Code Playgroud)

    这将显示一个空的小部件,其中没有按钮。与 whenbuttonWidget1对象的子级的行为相反,调用show()显示窗口小部件父级及其所有子级在里面。

    使用setEnabled(), setLayoutDirection(), ...时会发生类似的问题

  • button.pos()不会返回相对于 的坐标Widget1,实际上按钮甚至没有显示在里面。使用move().

  • 事件系统可能无法按预期工作。因此,如果成员小部件不处理某些鼠标/键盘事件,则该事件不会传播到小部件(因为没有指定小部件)。

但是可以编写第二种方法来利用与 RAII 的父关系,从而避免上述问题:

class Widget2 : public QWidget
{
public:
    explicit Widget2(QWidget* parent = nullptr):QWidget(parent){}

private:
    QPushButton button{this}; //C++11 member initializer list
};
Run Code Online (Sandbox Code Playgroud)

或者,在 C++11 之前的版本中:

class Widget2 : public QWidget
{
public:
    //initialize button in constructor, button's parent is set to this
    explicit Widget2(QWidget* parent = Q_NULLPTR):QWidget(parent), button(this){}

private:
    QPushButton button;
};
Run Code Online (Sandbox Code Playgroud)

这样,两种方法之间的 Qt 框架就没有任何区别。事实上,使用第二种方法可以避免不必要的动态分配(请注意,如果分配在某些嵌套循环中非常频繁地执行,例如,这可能会稍微提高性能。但是,对于大多数应用程序,性能在这里并不是真正的问题)。因此,您可能会像这样编写小部件:

class Widget : public QWidget
{
public:
    explicit Widget(QWidget* parent = nullptr):QWidget(parent){
        //add widgets to the layout
        layout.addWidget(&button);
        layout.addWidget(&lineEdit);
        layout.addWidget(&label);
    }
    ~Widget(){}

private:
    //widget's layout as a child (this will set the layout on the widget)
    QVBoxLayout layout{this};
    //ui items, no need to set the parent here
    //since this is done automatically in QLayout::addWidget calls in the constructor
    QPushButton button{"click here"};
    QLineEdit lineEdit;
    QLabel label{"this is a sample widget"};
};
Run Code Online (Sandbox Code Playgroud)

这很好,有人可能会说这些子小部件/对象将被销毁两次,第一次是在它们超出范围时,第二次是在它们的父级被销毁时,使这种方法不安全。这不是问题,因为一旦子对象被销毁,它就会将自己从其父对象的子对象列表中删除,请参阅文档。因此,每个对象只会被销毁一次

  • @MarekR 显然是过早的悲观化:您不仅必须再次动态分配,而且您还需要通过额外的间接层引用对象,从而浪费宝贵的缓存。 (2认同)