FAQ Qt FAQ Qt Creator FAQ PyQt & PySide

FAQ QtConsultez toutes les FAQ

Nombre d'auteurs : 26, nombre de questions : 298, dernière mise à jour : 6 mai 2017 

 
OuvrirSommaireGénéralités techniquesModèle objet de QtQObject

Dans un code Qt, on verra souvent un new sans delete associé.

En fait, si une nouvelle instance de QObject est créée et qu'on lui spécifie un parent - le premier argument du constructeur -, c'est ce parent qui sera chargé de la destruction du fils.

La majorité des classes de Qt hérite plus ou moins directement de QObject, mais attention, ce n'est pas le cas de toutes. L'indicateur est la possibilité de passer un objet parent au constructeur.

Mis à jour le 7 juillet 2009  par Matthieu Brucher

Lien : Allouer sur le tas ou sur la pile des QObject et dérivés ?

Cette erreur se produit lorsque la partie QObject d'une classe n'a pas été ajoutée à l'édition des liens.

Lors de l'utilisation de la macro Q_OBJECT, on définit un certain nombre de méthodes et de variables statiques. Ces méthodes et ces variables sont implémentées dans un fichier généré automatiquement par qmake à l'aide de l'outil moc.

Pour corriger le problème, ajoutez la macro Q_OBJECT dans votre déclaration de classe puis relancez qmake. Si le problème subsiste, vérifiez que le dossier du fichier source est bien présent dans les INCLUDEPATH du fichier de projet pour que le moc passe bien sur les fichiers y étant présents.

Mis à jour le 7 mai 2012  par Matthieu Brucher, Louis du Verdier

Lien : La classe QObject
Lien : Les signaux et slots

Chaque QObject contient une liste des pointeurs vers les QObject fils. Cette liste permet d'effacer automatiquement les objets fils de cette liste.

Cette liste implique que les objets fils héritant de QObject devraient être alloués sur le tas et non la pile. A priori, le code d'effacement est fait de telle sorte qu'il ne devrait pas y avoir de problème en allouant un objet sur la pile car la destruction d'un objet entraîne sa suppression dans la liste des parents. En revanche, effacer manuellement ou automatiquement par destruction dans la pile les objets fils peut entraîner un surcoût. Enfin, étant utilisé avec une sémantique de pointeurs, il vaut mieux utiliser les pointeurs.

 
Sélectionnez
{
    QObject parent;
    QObject *enfant = new QObject(&parent);
}
// enfant est correctement supprimé de la mémoire par parent

De même, une instance héritée de QObject ne peut appartenir à deux QObject.

 
Sélectionnez
    QObject *parent = new QObject;
    QObject *enfant = new QObject(parent);

    QObject autreParent;
    enfant->setParent(&autreParent); // change le parent de enfant

    delete parent;
    // enfant n'est pas supprimé, car parent ne "possède" plus enfant

La relation parent-enfant peut poser problème lorsque les QObject sont alloués sur la pile. Lorsqu'un objet est alloué sur la pile, et a un parent, le parent pourrait appeler delete sur l'objet qui est sur la pile.

 
Sélectionnez
{
    // !!! exemple de ce qu'il NE faut PAS faire, ne pas recopier !
    QPushButton button("button");
    QWidget widget;
    button.setParent(&widget);
} // plantage ici

// lorsque widget est détruit à la fin de la portée, il supprime button à l'aide de delete.
// button avait été alloué sur la pile
Mis à jour le 7 mai 2012  par Matthieu Brucher, Benjamin Poulain

Lien : QObject
Lien : Le modèle QObject

L'héritage multiple de plusieurs QObject n'est pas possible. En effet, l'architecture de Qt - les métaobjets - rend la chose impossible à l'heure actuelle.

Lorsque vous faites un héritage multiple, mettez en premier la classe héritant de QObject.

Mis à jour le 7 juillet 2009  par Matthieu Brucher

QObject (et toutes les classes qui en dérivent) ne se marie pas très bien avec les pointeurs intelligents classiques du C++, tels que auto_ptr ou shared_ptr. Comme expliqué dans la question Un new sans delete ?, Qt implémente via QObject un mécanisme de gestion de la mémoire dont le principe est qu'un QObject parent détruise automatiquement tous ses objets QObject enfants.

Ce mécanisme est fort pratique et permet de grandement simplifier la gestion de la mémoire. Cependant, il entre en conflit avec tout autre mécanisme de libération de la mémoire tel que ceux implémentés au moyen de pointeurs intelligents.

En effet, si vous déclarez un pointeur intelligent sur une instance de QObject, ce dernier aura en charge la destruction de cette instance. Si par malheur votre QObject se trouve être l'enfant d'un autre QObject, et que ce parent vient à être détruit, votre instance sera elle aussi automatiquement détruite, alors que votre pointeur intelligent continuera de pointer vers elle ! Et quand votre pointeur intelligent estimera qu'il est temps de la détruire, il effectuera un appel à delete sur un objet déjà détruit (double utilisation de delete) avec toutes les conséquences fâcheuses que l'on connaît.

À noter que spécifier un parent nul au moment de la construction de votre QObject ne vous garantit pas pour autant que ce dernier ne sera pas par la suite reparenté via la fonction setParent(), chose qui se produit assez régulièrement entre objets graphiques (QWidget).

En conséquence, il est déconseillé d'utiliser des pointeurs intelligents sur des objets de type QObject. Si jamais vous devez néanmoins y avoir recours, vous devez protéger votre instance d'une double suppression au moyen d'un pointeur protégé (QPointer avec Qt 4), comme dans l'exemple suivant.

 
Sélectionnez
std::shared_ptr<QPointer<QWidget> > ptr(new QPointer<QWidget> >( new QWidget() );
// Avec C++14, plus propre : 
std::shared_ptr<QPointer<QWidget> > ptr = std::make_shared<QPointer<QWidget>>( new QWidget() );

On a deux niveaux avec cette construction std::shared_ptr< QPointer<QWidget> > :

  • new QPointer<QWidget>(…). L'objet std::shared_ptr possède le pointeur vers QPointer<QWidget> : peu importe la situation du QWidget, il supprimera (delete) le pointeur vers le QPointer<QWidget> lorsque le dernier std::shared_ptr sera détruit ;
  • new QWidget(). Le QPointer, lui, a un pointeur vers le QWidget et n'en a pas la garde. Que le QWidget soit déjà détruit ou non, il ne le supprimera pas. Seul son pointeur interne se mettra à nullptr si le QWidget est détruit.

Finalement, lorsque le (dernier) std::shared_ptr est détruit, si le QWidget ne l'est pas, il y aura une fuite de mémoire !

Vu la lourdeur de cette écriture et le changement de sémantique introduit, on comprend mieux pourquoi il est déconseillé d'employer des pointeurs intelligents sur des objets dérivant de QObject.

Si vous souhaitez néanmoins utiliser des pointeurs intelligents dans ce contexte, vous pouvez utiliser la classe suivante, qui règle automatiquement les problèmes de propriété. (Elle utilise des fonctionnalités disponibles uniquement à partir de C++11.)

 
Sélectionnez
#ifndef SCOPED_QPOINTER_HPP
#define SCOPED_QPOINTER_HPP
 
#include <QPointer>
#include <memory>
#include <type_traits>
 
template <
    class Object,
    class Deleter = std::default_delete<Object>
> class ScopedQPointer {
public:
    static_assert(std::is_base_of<QObject, Object>{}, "Object must be derived from QObject");
 
    using object_type     = Object;
    using reference       = object_type &;
    using const_reference = object_type const &;
    using pointer         = object_type *;
    using const_pointer   = object_type const *;
 
    explicit ScopedQPointer(Object * p = nullptr) : qptr{p} {}
 
    ScopedQPointer(ScopedQPointer const &) = delete;
    ScopedQPointer & operator = (ScopedQPointer const &) = delete;
 
    ScopedQPointer(ScopedQPointer && other) Q_DECL_NOEXCEPT
      : qptr{ other.release() }
    { }
 
    ScopedQPointer & operator = (ScopedQPointer && other) Q_DECL_NOEXCEPT {
        ScopedQPointer{std::move(other)}.swap(*this);
        return *this;
    }
 
    ScopedQPointer & operator = (pointer p) {
        reset(p);
        return *this;
    }
 
    ~ScopedQPointer() {
        Deleter{}( qptr.data() );
    }
 
    bool isNull() const { return qptr.isNull(); }
 
    void swap(ScopedQPointer & other) Q_DECL_NOEXCEPT { qptr.swap(other.qptr); }
 
    void reset(pointer p = nullptr) {
        ScopedQPointer tmp{p};
        swap(tmp);
    }
 
    QPointer<object_type> release() {
        auto ret = qptr;
        qptr.clear();
        return ret;
    }
 
    pointer   data()        const { return qptr.data(); }
    pointer   operator ->() const { return  data(); }
    reference operator *()  const { return *data(); }
    operator pointer ()     const { return  data(); }
 
private:
    QPointer<object_type> qptr;
};
 
 
struct LateDeleter {
    template <class Object>
    void operator () (Object * p) const {
        if (p) {
            p->deleteLater();
        }
    }
};
 
template <class Object>
using ScopedQPointerDeleteLater = ScopedQPointer<Object, LateDeleter>;
 
#endif // SCOPED_QPOINTER_HPP
Mis à jour le 8 avril 2017  par Aurélien Régat-Barrel, Winjerome

Lien : FAQ C++ : pointeurs intelligentsFAQ C++ : pointeurs intelligents
Lien : Les pointeurs intelligents, de Loïc Joly

Faire un delete sur un QObject peut être très dangereux. Par exemple, avec le système de signaux et de slots, il est parfois difficile d'être sûr qu'il n'est pas utilisé par la pile d'appels au moment du delete : on veut détruire un bouton quand on clique dessus.

 
Sélectionnez
class widget : public QWidget
{
Q_OBJECT
QPointer<QPushButton>  m_but ;
public :
    widget ()
    {
        m_but = new QPushButton(this);
        connect(m_but, SIGNAL(clicked()), this, SLOT(butClicked()));
    }
public slots :
    void butClicked()
    {
        delete m_but;
        m_but = 0;
    }
} ;

Lorsque l'on appuie sur le bouton, le signal clicked est émis, ce qui appelle directement le slot butClicked(). À ce niveau, la pile d'appels correspond à :

 
Sélectionnez
widget => butClicked()
m_but => clicked()

Ainsi, le bouton sur lequel on veut faire un delete est utilisé par la pile d'appels. Le delete va produire une erreur mémoire et votre application se retrouve en état indéterminé.

Pour cela, QObject fournit la fonction deleteLater, qui va poster un événement et le QObject sera détruit par la boucle d'événements dès que tout danger est écarté.

La fonction devient tout simplement :

 
Sélectionnez
void butClicked()
{
    m_but->deleteLater();
}

Il est intéressant de constater que cette fonction est un slot et peut donc être connectée à un signal.

Il est aussi possible d'appeler plusieurs fois cette fonction sans aucun danger.

Mis à jour le 7 mai 2012  par Yan Verdavaine

Lorsque l'on utilise un QObject (ou un enfant) en membre d'une classe, on peut se demander s'il faut utiliser un pointeur ou non. Voici quelques conseils.

  • Objet optionnel : si l'objet est optionnel, il est fortement conseillé d'utiliser le pointeur intelligent QWeakPointer, qui permet de savoir si le QObject pointé existe (contrairement à QPointer).
  • Les objets partagés entre classes : les QObject ne sont pas prévus pour être utilisés par plusieurs classes en même temps. Le problème ne devrait donc pas se poser.
  • Les QWidget : comme ils peuvent être reparentés (par les dispositions, par exemple), il est fortement conseillé d'utiliser des pointeurs pour garantir leur destruction de manière correcte.
  • Les autres : au choix. Il peut être intéressant de ne pas utiliser de pointeur pour éviter de faire les allocations à la main. Ceci ne pose aucun problème avec le système de destruction parent/enfant, car ils seront détruits avant la classe. Toutefois, il ne faut pas oublier de mettre this en parent.
 
Sélectionnez
class monObj : QObject
{
QTimer m_timer;
public :
    monObj ()
    : m_timer(this)
    {
    }
}
 
Sélectionnez
class monObj : QObject
{
QTimer m_timer;
public :
    monObj ()
    {
        m_timer.setParent(this);
    }
}

Il est très important que tous les QObject en membre d'une classe aient pour parent this. QWidget est un cas particulier où le parent dépend de son niveau dans l'arbre parent/enfants des widgets et où le parent est forcément un QWidget.

Mis à jour le 8 avril 2017  par Yan Verdavaine, Thibaut Cuvelier
  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 - 2017 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.