Chapter 8.  QObject, QApplication, Signals, and Slots

Table of Contents

8.1. Values and Objects
8.2. Composite Pattern: Parents and Children
8.2.1. Finding Children
8.2.2. QObject's Child Managment
8.2.2.1. Exercises: QObject's Child Managment
8.3. QApplication and the Event Loop
8.4. Q_OBJECT and moc: A checklist
8.5. Signals and Slots
8.5.1. Points of Departure
8.6. QObject Lifecycle
8.7. QTestLib
8.8. Exercises: QObject, QApplication, Signals, and Slots
8.9. Review Questions

[ fromfile: qobject.xml id: qobject ]

Abstract

QObject is the base class for many of the important classes in the Qt library, such as QEvent, QApplication, QLayout, and QWidget. A QObject can have a parent and children, providing another implementation of the Composite pattern. It can use signals and slots, an implementation of the Observer pattern, to communicate with other QObjects. QObjects make it possible to do event-based programming, which employs QApplication and Qt's event loop.

We refer to any object of a class publicly derived from QObject as a QObject. Following is an abbreviated look at its definition.

  class QObject {
    public:
      explicit QObject(QObject* parent=0);
      QObject * parent () const;
      QString objectName() const;
      void setParent ( QObject * parent );
      const QObjectList & children () const;
      // ... more ...
  };
 

QObject does not have a public copy constructor or copy assignment operator. Down toward the end of its class definition there is a macro (Q_DISABLE_COPY(QObject)) that explicitly makes sure that no QObject can be copied. QObjects are not meant to be copied. In general, QObjects are intended to represent unique objects with identity; that is, they correspond to real-world things that have some sort of persistent identity.

One immediate consequence of this no-copy policy is that a QObject can never be passed by value to any function. Copying a QObject's data members into another QObject is still possible, but the resulting two QObjects are considered distinct.

[Note] explicit Constructors

Single-argument constructors of QObject (and derived classes) should be designated explicit to avoid accidental conversions from taking place.[60] QObject is not intended to be a holder of a temporary value, and it should not be possible to create one implicitly from a pointer or a simple value.

Each QObject can have (at most) one parent QObject, and an arbitrarily large number of QObject children. In other words, the type of each child must be QObject or must be derived from QObject. Each QObject stores pointers to its children in a QObjectList[61]. The list itself is created in a lazy-fashion to minimize the overhead for objects that have no children. Because each child is a QObject and can have an arbitrarily large collection of children, it is easy to see why copying QObjects is not permitted.

The notion of children can help to clarify the notion of identity and the no-copy policy for QObjects. If you represent individual humans as QObjects, the idea of a unique identity for each QObject is clear. Also clear is the idea of children. The rule that allows each QObject to have at most one parent can be seen as a way to simplify the implementation of this class. Finally, the no-copy policy stands out as a clear necessity. Even if it were possible to "clone" a person (i.e., copy its data members to another QObject), the question of what to do with the children of that person makes it clear that the clone would be a separate and distinct object with a different identity.

Each QObject parent manages its children. This means that the QObject's children are destroyed during its destructor call.

The child list establishes a bidirectional association between QObjects:

Setting the parent of a QObject implicitly adds its address to the child list of the parent; i.e.,

 objA->setParent(objB); 

adds the objA pointer to the child list of objB. If you subsequently have

 objA->setParent(objC); 

then the objA pointer is removed from the child list of objB and added to the child list of objC. Such an action is called reparenting.

As you saw earlier, the widgets of a graphical user interface (GUI) are all derived from QWidget, which is derived from QObject. As with QObjects, we refer to any object of a class publicly derived from QWidget as a QWidget (or sometimes, simply, widget). In a GUI, the parent-child relationships are usually visible: Child widgets appear inside parent widgets. In Figure 8.1, the dialog widget has several children, including: A label widget, a line edit widget, and two pushbutton widgets. It also has a title bar widget, which may be a parent or sibling of the dialog. It is usually provided by the window manager, and contains several child widgets, including the button widgets that enable a user to minimize, maximize, or close the dialog.

The need for the child management requirement is also visible in Figure 8.1. When you close the dialog (e.g., by clicking on the small X button in the upper-right corner), you want the entire dialog window to vanish from the screen (i.e., to be destroyed). You don't want pieces of it (e.g., the odd button or label widget) to linger on the screen, and you don't want to put the burden of all that cleanup on the programmer. That is why each QObject is responsible for destroying all its children when its destructor is called. This is a naturally recursive process. When a QObject is to be destroyed, it must first call the destructor for each of its children; each child object must then call the destructor for each of its children; and so forth.

Figure 8.1.  Parent-Child Object Versus Base-Derived Class

Parent-Child Object Versus Base-Derived Class

Now consider some of the problems that would arise if it were possible to copy a QObject. For example, should the copy have the same parent as the original? Should the copy have (in some sense) the children of the original? A shallow copy of the child list would not work because then each of the children would have two parents. In that case, if the copy needs to be destroyed (e.g., if the copy was a value parameter in a function call), each child needs to be destroyed too (because the QObject must manage its children). Even with resource sharing methods, this approach would introduce some serious difficulties. A deep copy of the child list could be a costly operation if the number of children were large and the objects pointed to were large. Because each child could also have arbitrarily many children, this questionable approach would also generate serious difficulties.



[60] Section 2.12 discusses the keyword explicit.

[61] QObjectList is a typedef (i.e., an alias) for QList<QObject*>