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

时间:2016-10-08 11:19:33

标签: 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);"
};

2)使用RAII

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

private:
    QPushButton button; // normal RAII
};

通常,我使用第一种方法。如果父母不仅通过布局知道其子女,那么某事似乎可以更好地发挥作用。但想一想......对我来说,真正的原因并不十分清楚。

我知道堆栈是有限的。但是,让我们说这不起作用。毕竟,堆栈不是 小。

2 个答案:

答案 0 :(得分:4)

  

如果父母不仅通过布局知道其子女,那么 似乎可以更好地工作。

你是对的。 QObject的父级不仅用于内存管理,this answer总结了其他一些用法。这里最重要的是QWidget的问题(因为您担心添加成员QWidget' s),所以如果您使用第二种方法,那么写它,这里有一些你可能得到的问题:

  • 假设您正在实例化Widget1并在主函数中显示它,如下所示:

    Widget1 w;
    w.show();
    

    这将显示一个没有按钮的空小部件。与buttonWidget1对象的子项时的行为相反,其中调用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
};

或者,在pre-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;
};

这种方式与两种方法之间的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"};
};

这非常好,可以说这些子窗口小部件/对象将被销毁两次,首先是在它们超出范围时,第二次是在它们的父级被销毁时,使得该方法不安全。这是一个问题,因为一旦子对象被销毁,它将自己从其父级子列表中删除,请参阅docs。因此,每个对象将被完全销毁一次

答案 1 :(得分:-1)

首先,您要问的是C ++面向对象编程的基础知识。 适用对象破坏的标准规则。

其次你错了。这里的堆栈不涉及。您将窗口小部件作为类字段放置,以便窗口小部件成为您的类的一部分。只有在堆栈上实例化Widget1时,我才会位于堆栈上。

堆叠的大小在这里无关紧要。 Qt在所有QObjects中使用d指针代码模式。这意味着Qt类通常具有较小的常量(我没有检查,但很可能在32位系统上它将是8个字节:指向虚拟表和d指针的指针)。 即使您在堆栈上实例化您的类的对象,编译器完成的数据对齐也可以将两种情况下堆栈内存使用的差异减少到零(类的大小通常由编译器向段落大小 - 16字节舍入),所以在您的示例中没理由担心。

如果你将一个按钮作为Widget1的一部分放在一起,那么与你的应用程序逻辑观点没有太大的区别(你写过“父亲不仅仅通过布局知道它的孩子”这意味着{{ 1}}始终是Widget1)的父级。

您应该使用带指针的模式来保持代码清洁。 在维护良好的项目中,保持头文件尽可能干净是很重要的。这意味着您应该使用前向声明并避免头文件中不必要的包含,例如:

button

前向声明用于大项目,因为它减少了建设时间。如果项目很大,这可能非常重要。 如果您将widget用作直接值,则必须包含其头文件“QPushButton.h”。