Что такое родитель в Qt?

Почти каждый класс QtWidgets может иметь родителя. И обычно необязательно устанавливать родителя при инициализации объекта. Например, если я создам класс, который наследует класс QWidget, я сделаю в конструкторе следующее:

Widget::Widget(QWidget* parent): QWidget(parent) {
    hbox = new QHBoxLayout(this);
    yes_button = new QPushButton("&Yes");
    no_button = new QPushButton("&No", this);
    cancel_button = new QPushButton("&Cancel", hbox);
}

Я могу установить или не установить parent. Я могу сделать cancel_button дочерним элементом hbox. Я также могу сделать cancel_button дочерним элементом yes_button, но я думаю, что это плохой поступок.

какой в ​​этом смысл? И действительно ли необходимо устанавливать родительский элемент для каждого класса на основе QWidget, который я создаю?


person Mas Bagol    schedule 20.05.2015    source источник
comment
Вы заметили, что QWidget происходит от QObject?   -  person cmannett85    schedule 20.05.2015
comment
Да, конечно. Но я не могу понять, в чем смысл этих родительских отношений.   -  person Mas Bagol    schedule 20.05.2015


Ответы (2)


Помимо помощи с порядком прорисовки в объектах GUI, это также помогает с управлением памятью, так что, когда вы уничтожаете QObject, все его дочерние элементы также уничтожаются. См. http://doc.qt.io/qt-4.8/objecttrees.html для более подробной информации. Когда что-то изменяется в родительском элементе (например, при изменении его размера), он может уведомить своих дочерних элементов, чтобы они также обновили себя.

Чтобы ответить на ваш вопрос, вы не обязаны устанавливать родитель для всего (в конце концов, именно поэтому это необязательный параметр), но в большинстве случаев лучше установить его правильно.

person Vitor    schedule 20.05.2015
comment
Есть ли пример, что я должен установить родительский класс на основе QWidget. Случай, что программа не будет работать должным образом без установки родителя? - person Mas Bagol; 20.05.2015
comment
Если вы не установите родителя правильно, менеджеры компоновки не будут работать. Другая проблема заключается в том, что в вашем примере, если вы уничтожите свой QWidget, вам придется вручную уничтожить yes_button, иначе произойдет утечка. Если родитель был установлен, Qt позаботится об этом за вас. - person Vitor; 20.05.2015
comment
Обратите внимание, что существует особый случай с QWidget. Если у него нет родителя, оно преобразуется в окно верхнего уровня (см. здесь). - person rbaleksandar; 20.02.2016

Во-первых, QWidget — это QObject, а QObject — это узлы в дереве QObject. Дочерние узлы управляются памятью родителем, если только вы не освободите их до того, как родитель сможет это сделать. Таким образом, управление памятью является одной из причин, по которой виджеты или любые другие QObject имеют родителя.

Во-вторых, видимые виджеты без родителей всегда являются окнами верхнего уровня. И наоборот, невозможно иметь виджет не верхнего уровня, который не имеет родителей. Когда вы показываете виджет без родителей, он получает собственное окно. Обратное не всегда верно — дочернему виджету можно присвоить флаг Qt::Window, и он также станет окном верхнего уровня.

Следствием этого является то, что любой виджет, содержащийся в других виджетах, имеет родителя, иначе это было бы окно верхнего уровня. Этот родитель может быть не установлен вами явно, но тем не менее он установлен.

Я думаю, что ваш вопрос можно перефразировать так: когда мне нужно явно указать родителя конструктору виджета? Ответ:

  1. Всякий раз, когда виджет является окном верхнего уровня, у которого вы хотите иметь родителя. Такие окна не подлежат управлению компоновкой, поэтому нет механизма для установки этого родителя за вас. Временные диалоги верхнего уровня должны иметь родителей, чтобы они правильно располагались по отношению к родительскому окну.

  2. Всякий раз, когда у вас есть дочерний виджет, не подлежащий управлению макетом.

Виджеты, подлежащие управлению макетом, становятся родительскими при вставке в макет:

int main(int argc, char ** argv) {
  QApplication app(argc, argv);
  QWidget window;
  QVBoxLayout layout(&window);
  QLabel label("Hello");
  QPushButton button("Goodbye");
  layout.addWidget(&label);
  layout.addWidget(&button);
  QObject::connect(&button, &QPushButton::clicked, [&app]{ app.quit(); });
  window.show();
  return app.exec();
}

Наконец, не все виджеты или QObject должны быть явно созданы в куче. Поскольку все производные от QObject классы в Qt (и многие другие классы тоже!) используют идиому PIMPL, когда вы выделяете их по отдельности в куче, вы фактически выполняете выделение кучи дважды. Сначала вы выделяете экземпляр класса — иногда экземпляр имеет размер всего один или два указателя, — а затем конструктор класса выделяет свой PIMPL. Явное выделение кучи — это случай преждевременной пессимизации.

Чтобы избежать этой пессимизации, ваш Widget должен выглядеть следующим образом:

class Widget : public QWidget {
  Q_OBJECT
  QHBoxLayout m_layout;
  QPushButton m_yesButton, m_noButton, m_cancelButton;
public:
  Widget(QWidget * parent = 0);
};

Widget::Widget(QWidget * parent) : 
  QWidget(parent),
  m_layout(this),
  m_yesButton("&Yes"),
  m_noButton("&No"),
  m_cancelButton("&Cancel")
{
  m_layout.addWidget(&m_yesButton);
  m_layout.addWidget(&m_noButton);
  m_layout.addWidget(&m_cancelButton);
}

Если вы хотите использовать идиому PIMPL, вы также можете это сделать:

// Widget.h - Interface
class WidgetPrivate;
class Widget : public QWidget {
{
  Q_OBJECT
  Q_DECLARE_PRIVATE(Widget)
  QScopedPointer<WidgetPrivate> const d_ptr;
public:
  Widget(QWidget * parent = 0);
  ~Widget();
};

// Widget.cpp - Implementation 
class WidgetPrivate {
  Q_DISABLE_COPY(WidgetPrivate)
  Q_DECLARE_PUBLIC(Widget)
  Widget * const q_ptr;
  QHBoxLayout layout;
  QPushButton yesButton, noButton, cancelButton;
public:
  WidgetPrivate(Widget * q);
};

WidgetPrivate::WidgetPrivate(Widget * q) {
  q_ptr(q),
  layout(q),
  yesButton("&Yes"),
  noButton("&No"),
  cancelButton("&Cancel")
{
  layout.addWidget(&yesButton);
  layout.addWidget(&noButton);
  layout.addWidget(&cancelButton);
}

Widget::Widget(QWidget * parent) :
  QWidget(parent),
  d_ptr(new WidgetPrivate(this))
{}

Widget::~Widget() {}
// necessary, since WidgetPrivate is unknown to the interface!

Конечно, вы должны использовать QDialogButtonBox вместо всего этого :)

person Kuba hasn't forgotten Monica    schedule 20.05.2015
comment
Очень полезно. Означает ли это, что комментарий @vitor выше (если вы неправильно установите родителя, менеджеры компоновки не работают...) неверен? - person djvg; 22.03.2018
comment
Родитель должен быть установлен правильно. Это просто не означает, что для этого нужно писать код. Добавление виджета в макет всегда правильно устанавливает родителя. Так что утверждение Витора не обязательно неверно — оно просто вряд ли когда-нибудь окажется правдой. Вы должны написать код для изменения родителя постфактум, чтобы это утверждение стало истинным. Это не произойдет по упущению или случайности. - person Kuba hasn't forgotten Monica; 22.03.2018