What Are Consequences of Forcing Qobject as a Parent of Qwidget

What are consequences of forcing QObject as a parent of QWidget?

What are consequences of forcing QObject as a parent of QWidget?

Irrevocable undefined behavior.

Qt is not designed to support a non-widget parent to a QWidget. I consider it an API bug in Qt, since a QWidget isn't fully a QObject in the Liskov Substitution Principle sense because of that limitation.

Qt 4.x will crash when attempting to activate the widget. So it'll work until you focus your application and then will crash.

Qt 5.x asserts in QObject::setParent().

The assertion can be bypassed, though:

// https://github.com/KubaO/stackoverflown/tree/master/questions/widget-parent-28992276
#include <QApplication>
#include <QLabel>

class ParentHacker : private QWidget {
public:
static void setParent(QWidget * child_, QObject * parent) {
// The following line invokes undefined behavior
auto child = static_cast<ParentHacker*>(child_);
Q_ASSERT(child->d_ptr->isWidget);
child->d_ptr->isWidget = 0;
child->QObject::setParent(parent);
child->d_ptr->isWidget = 1;
}
};

int main(int argc, char ** argv) {
QApplication app{argc, argv};
QLabel w{"Hello!"};
w.setMinimumSize(200, 100);
w.show();
ParentHacker::setParent(&w, &app);
return app.exec();
}

It will crash somewhere else then.

You'd be fighting an uphill battle trying to patch Qt to get it to work. It's not a worthwhile fight, I think - not unless a decision is made to make a QWidget truly-a QObject and change its constructor signature. That can be done at the earliest in Qt 6 since it's a binary-incompatible change AFAIK.

Moreover, what you're trying to do is mostly unnecessary. You can certainly have a hidden QWidget parent to multiple stand-alone top-level widgets.

#include <QApplication>
#include <QLabel>

int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget parent;
QLabel l1{"Close me to quit!"}, l2{"Hello!"};
for (auto label : {&l1, &l2}) {
label->setMinimumSize(200, 100);
label->setParent(&parent);
label->setWindowFlags(Qt::Window);
label->setText(QString("%1 Parent: %2.").
arg(label->text()).arg((quintptr)label->parent(), 0, 16));
label->show();
}
l2.setAttribute(Qt::WA_QuitOnClose, false);
return app.exec();
}

The overhead of having the widget hidden is minimal, you're not wasting any resources by using a QWidget instead of a QObject for the parent.

Is it possible to have a QWidget as a child to a QObject?

Connecting signals to slots manually is perfectly fine. Qt itself is doing that, most Qt applications are doing that.

I'm afraid you can't use connectSlotsByName for the parent-child issues with QWidget, but if you really want it, you have all the metadata available in QMetaObject, so you can write a function that works like connectSlotsByName on any pair/set of QObjects.

There's a widget declaration order to follow?

TL;DR: Yes! The order of declarations has a strictly defined meaning in C++. A random order will not work, as you've happened to notice.

You're not showing all the code. What is important is that one of the widgets is a child of the group box. Suppose you had:

class ConfigDialog : public QDialog
{
// WRONG
Q_OBJECT
QLabel userLabel;
QGroupBox userAuthBox;
QGridLayout userAuthLayout;
QVBoxLayout dialogLayout;
public:
ConfigDialog(QWidget * parent = 0) :
QDialog(parent),
dialogLayout(this),
userAuthLayout(&userAuthBox) {
// Here userLabel is parent-less.
Q_ASSERT(! userLabel.parent());
userAuthLayout.addWidget(&userLabel, 0, 0);
// But here userLabel is a child of userAuthBox
Q_ASSERT(userLabel.parent() == &userAuthBox);
}
};

The default destructor will invoke the destructors in the following order - it literally is as if you wrote the following valid C++ code in the destructor.

  1. dialogLayout.~QVBoxLayout() - OK. At this point, the dialog is simply layout-less. All the child widgets remain.
  2. userAuthLayout.~QGridLayout() - OK. At this point, the group box is simply layout-less. All the child widgets remain.
  3. userAuthBox.~QGroupBox() - oops. Since userLabel is a child of this object, the nested userAuthox.~QObject call will execute the eqivalent of the following line:

    delete &userLabel;

    Since userLabel was never allocated using new, you get undefined behavior and, in your case, a crash.

Instead, you should:

  1. Declare child widgets and QObjects after their parents.

  2. Use C++11 value initialization if possible, or initializer lists in the constructor to indicate to the maintainer that there is a dependency between the children and the parents.

See this answer for details and a C++11 and C++98 solution that will force the mistakes to be caught by all popular modern static C++ code analyzers. Use them if you can.

How to make event filter that deletes itself when corresponding QObject has been deleted?

I thought that hierarchy only works for widgets.

The object ownership is implemented by QObject. The QWidget simply inherits from QObject. In fact, QWidget is a bit of a special case: it can have QObject children, but can't have a parent that's not a QWidget.

All you need to do is parent the filter to the object it's installed on:

MyEventFilterObject::MyEventFilterObject(QObject* parent) : QObject(parent)
{
//...
}

someQObject.installEventFilter(new MyEventFilterObject(someQObject));

Does QObject emit any event when it's deleted?

It's hard to talk of an object emitting an event. Events are targeted at a particular receiver. The sender of the event must be aware of the recipient a-priori. An event can be sent at any time, to any object, by any code - even code running in a non-Qt thread and not using any other aspect of Qt functionality. In case of a generic QObject, it could send an event to its parent and/or its children, and that's about it. Otherwise, it has no knowledge of any other objects.

QObject does emit a signal from its destuctor, though: QObject::destroyed(QObject*). You could have the event filter destroy itself when that signal is emitted:

QObject * chainDestroy(QObject * src, QObject * dst) {
QObject::connect(src, &QObject::destroyed, dst, &QObject::deleteLater);
return dst;
}
auto filter = chainDestroy(&someQObject, new MyEventFilterObject);
someQObject.installEventFilter(filter);

If you can, simply set the parent on the filter. If the filter needs to have a different parent, then connect the signal as above.

how can I know that it's been deleted and delete the event filter?

You could simply make the event filter a permanent object - you only need one per thread. One event filter can be installed on as many objects as you wish, as long as they all live in the same thread. You can certainly do that unless the event filter is stateful, and its state is specific to the object it's filtering on.

QObject auto delete order

Once delete is called on a, its children b and c are delete'd as well, in the same order in which they were added to a's children collection.
To demonstrate this, you can create b and c without the parent argument, then call setParent later: the order of destruction will be the same order of the calls.

How should I store pointers in Qt?

First, hold things by value where you can. View each use of new, make_unique and make_shared with suspicion - you must justify each dynamic object creation. If a sub-object has the same lifetime as the parent, holding by value is a no-brainer. For example:

class MyWidget : public QWidget {
Q_OBJECT
QGridLayout m_topLayout{this};
QLabel m_sign{"Hello World"};
public:
MyWidget(QWidget * parent = nullptr) : QWidget{parent} {
m_topLayout.addWidget(&m_sign, 0, 0);
}
};

You're passing pointers around, but object ownership is clear and there's no change of ownership. Just because a QObject has a parent doesn't mean that the parent "owns" it. If the child is destructed before the parent, the ownership ceases. By using C++ semantics - namely the well-defined order of member construction and destruction - you have full control over child lifetimes and no QObject parent gets to interfere.

If you have non-movable objects that have one owner, use std::unique_ptr and move it around. That's the way to pass dynamically created QObjects around your own code. You can remove them from the pointer at the point where you make their ownership managed by a QObject parent, if there is such.

If you have objects with shared ownership, where their life should end as soon as possible (vs. when the application terminates, or some long-lived object gets destroyed), use std::shared_ptr. Ensure that the pointer outlives the users. For example:

class MyData : public QAbstractItemModel { /* ... */ };

class UserWindow : public QWidget {
Q_OBJECT
std::shared_ptr<MyData> m_data; // guaranteed to outlive the view
QTreeView m_view;
public:
void setData(std::shared_ptr<MyData> && data) {
m_data = std::move(data);
m_view.setModel(m_data.data());
}
};

This example is perhaps contrived, since in Qt most users of objects watch the object's destroyed() signal and react to the objects destruction. But this makes sense if e.g. m_view was a third-party C API object handle that had no way of tracking the data object's lifetime.

If the object's ownership is shared across threads, then the use of std::shared_ptr is essential: the destroyed() signal is only usable within a single thread. By the time you get informed about object deletion in another thread, it's too late: the object has been already destroyed.

Thirdly, when you return instances of dynamically created objects from factory methods, you should return them by a naked pointer: it's clear that a factory creates an object for someone else to manage. If you need exception safety, you can return a std::unique_ptr instead.

Promote custom widget with custom constructor in QT Creator

A widget whose parent can't be a QWidget is not a widget anymore. Your design breaks the Liskov Substitution Principle and has to be fixed.

You're free to enable special functionality if the widget happens to be of a certain type, but a widget must be usable with any widget for a parent.

Thus:

CustomWidget(QWidget* parent = nullptr) :
QWidget(parent)
{
auto customParent = qobject_cast<CustomWidget*>(parent);
if (customParent)
_specialValue = customParent->specialValue();
}

or:

class CustomWidget : public QWidget {
Q_OBJECT
CustomWidget *_customParent = qobject_cast<CustomWidget*>(parent());
SpecialType _specialValue = _customParent ? _customParent->specialValue() : SpecialType();

SpecialType specialValue() const { return _specialValue; }
public:
CustomWidget(QWidget * parent = nullptr) : QWidget(parent) {}
};

Is it safe to use *virtual* multiple inheritance if QObject is being derived from DIRECTLY?

No, multiple inheritance from QObject is not supported by Qt in any way.

The problem is not with virtual inheritance, it's Qt's meta-object system. Each QObject base class has an associated QMetaObject which manages signals, slots, properties, etc, and each meta-object knows its parent QObject so e.g. signals which exist in parent classes can be handled. The Qt moc is not able to deal with multiple inheritance from QObject or any of its sub-classes.

Declare abstract signal in interface class

As I found out in the last days... the Qt way of doing this is like this:

class IEmitSomething
{
public:
virtual ~IEmitSomething(){} // do not forget this

signals: // <- ignored by moc and only serves as documentation aid
// The code will work exactly the same if signals: is absent.
virtual void someThingHappened() = 0;
}

Q_DECLARE_INTERFACE(IEmitSomething, "IEmitSomething") // define this out of namespace scope

class ImplementEmitterOfSomething : public QWidget, public IEmitSomething
{
Q_OBJECT
Q_INTERFACES(IEmitSomething)

signals:
void someThingHappended();
}

Now you can connect to those interface signals.

If you don't have access to the implementation when connecting to the signal your connect statement will require a dynamic cast to QObject:

IEmitSomething* es = ... // your implementation class

connect(dynamic_cast<QObject*>(es), SIGNAL(someThingHappended()), ...);

... and this way you're not forced to expose the implementation class to subscribers and clients. Yeah!!!



Related Topics



Leave a reply



Submit