Stocker et sérialiser des objets avec Qt

Formation

En Semi-présenciel Paris

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En semi-présentiel

  • Lieu

    Paris

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Les sites et dates disponibles

Lieu

Date de début

Paris ((75) Paris)
Voir plan
7 Cité Paradis, 75010

Date de début

Consulter

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Le programme

Introduction du cours

Pour pouvoir comprendre parfaitement (et exploiter au maximum de ses capacités) ce tutoriel, il faut que vous connaissiez le cours de M@teo21 sur le C++ jusqu'à la partie 2 (non incluse si vous connaissez déjà Qt).

Il arrive forcément, comme à tout programmeur qui se respecte, un jour où on a besoin de manipuler le contenu d'un objet, par exemple pour le placer dans un fichier (ce qui est souvent le cas, mais cela peut être pour une autre raison). En d'autres termes, vous souhaitez sérialiser votre objet.

Dans le cas d'un objet simple (avec seulement quatre entiers par exemple), on peut se débrouiller tout seul. Mais imaginez maintenant que vous ayez un objet contenant les listes des chaînes de caractères, ainsi que plein d'autres valeurs... Tout à coup, ça devient beaucoup plus compliqué.

C'est pourquoi je vous propose, par le biais de ce tutoriel, de découvrir les outils proposés par Qt, à travers la classe QVariant, qui vont vous faciliter grandement les choses.

Pourquoi utiliser Qt ?

Je pense qu'il est très important de savoir que la sérialisation des objets n'est pas proposée que par Qt. En effet, de nombreuses bibliothèques proposent des outils analogues à ceux de Qt.

Pourquoi utiliser Qt, et pas une autre bibliothèque comme Boost ?

Je pourrais être tenté de vous répondre simplement "pourquoi pas ?", mais le choix de Qt ne relève pas d'une simple décision arbitraire.

Le premier avantage de Qt est que lorsqu'on souhaite sérialiser un objet, on n'a pas besoin de modifier le code de l'objet concerné. Si on prend exemple sur la bibliothèque Boost qui propose aussi une manière de sérialiser vos objets, Boost vous impose d'ajouter une fonction serialise.
Dans le cas où vous utilisez Qt, toutes les opérations de sérialisation se déroulent hors de la classe. Cet avantage peut aussi permettre la sérialisation d'objets auxquels on n'a pas accès au code source, comme c'est souvent le cas pour des objets situés dans des bibliothèques.

La seconde raison de mon choix vient du fait que, contrairement à Boost, Qt utilise une seule et unique classe pour vous permettre de sérialiser vos objets. Donc pas besoin de plonger à chaque fois dans la documentation d'une demi-douzaine de classes pour réussir votre sérialisation.

Et enfin, la dernière raison est qu'avec Qt, il y a très peu de code à ajouter pour sérialiser un objet, et que celui-ci peut être placé à peu près n'importe où.

Sérialiser avec QVariant

Pour débuter, je vous propose d'apprendre à sérialiser vos objets.

Désolé de déjà t'interrompre, mais tu entends quoi par "sérialiser" ?

Pour tout savoir, allez sur wikipédia. Même si ce n'est pas très clair, continuez à lire ce tutoriel, vous verrez vite à quoi ça peut servir.

Afin de simplifier la sérialisation d'objet, Qt à créé une classe dédiée à la sérialisation : QVariant. D'une manière extrêmement générale, on peut considérer que QVariant est la représentation d'un objet quelconque sérialisé.
Le fonctionnement de QVariant est très simple : à partir du moment où un objet est sérialisable (on verra un peu plus bas comment faire pour rendre un objet sérialisable), on peut sérialiser l'objet et le placer dans un QVariant. Ensuite vous en faites ce que vous voulez.

Pour éviter l'apparition de multiples sérialisations des objets "de base" de Qt, ceux-ci ont déjà été déclarés comme sérialisables. Donc QVariant fonctionne déjà avec la plus grande partie des objets du module QtCore, ainsi qu'une partie de ceux du module QtGui. Une liste (malheureusement incomplète) des objets sérialisables avec QVariant est disponible ici.

On va donc procéder en deux parties : dans un premier temps, on va voir comment faire pour utiliser QVariant avec des objets sérialisables. Puis ensuite, on va voir comment faire pour rendre un objet quelconque sérialisable.

QVariant et les objets sérialisables

On dispose de trois façons pour créer un QVariant à partir d'un objet sérialisable (ça permet une manipulation plus aisée) :

  • Par le constructeur : c'est la méthode par défaut, mais elle n'est possible qu'avec les objets sérialisables de Qt (donc pas avec vos objets sérialisables) ;

  • Par la méthode setValue, qui affecte une copie de l'objet au QVariant concerné ;

  • Par la méthode (statique) fromValue, qui crée un nouveau QVariant à partir d'une copie de l'objet donné.

Les deux dernières méthodes citées ne fonctionnent pas avec le compilateur MSVC 6. Si c'est celui dont vous vous servez, vous pouvez utiliser respectivement les fonctions qVariantSetValue et qVariantFromValue.

On distingue encore trois manières de récupérer la classe voulue à partir d'un QVariant (dans ce cas là, on parle de désérialisation) :

  • Par les fonctions de la forme "toClass", où Class représente le type à obtenir (exemple : toInt pour obtenir un entier, ou encore toString pour renvoyer une QString). Ces fonctions ne sont disponibles que pour les objets sérialisables de Qt ;

  • Par la fonction value, qui renvoie la classe demandée (exemple : var.value<QString>() qui renvoie un QString) ;

  • Par la fonction qvariant_cast, qui s'emploie comme la précédente : qvariant_cast<QString>(var); .

Comme précédemment, la fonction value ne marche pas avec MSVC 6. Dans ce cas, vous pouvez employer la fonction qVariantValue.

Dans la suite de ce tutoriel, je vais utiliser les fonctions setValue et value, mais vous pouvez vous servir de celles que vous voulez.

Voici un petit exemple qui montre la sérialisation puis la désérialisation d'une chaîne de caractère (QString) et puis qui compare le résultat :

#include <QCoreApplication> #include <QString> #include <QVariant> #include <iostream> int main (int argc, char ** argv) { QCoreApplication app(argc, argv); QString initial = "Ceci est le texte à sérialiser", final = ""; QVariant chaine_serialise; chaine_serialise.setValue(initial); final = chaine_serialise.value<QString>(); if(initial == final) { std::cout << "Les deux chaines sont identiques" << std::endl; } else { std::cout << "Les deux chaines sont differentes" << std::endl; } getchar(); app.quit(); return 0; }

N'oubliez pas de rajouter "CONFIG += console" à votre .pro, sinon la console n'affichera rien.

Juste avec la sérialisation des objets de Qt, on dispose déjà de quelque chose d'extrêmement puissant. Mais Qt fourni aussi la possibilité de rendre sérialisable n'importe quel objet, même appartenant à une autre librairie dont le code source ne serait pas disponible.

Rendre un objet quelconque sérialisable

Bon, malgré le coté parfait de QVariant, on ne peut pas tout faire non plus :o. Afin de pouvoir rendre sérialisable un objet, celui-ci doit respecter les trois règles suivantes :

  • L'objet doit posséder un constructeur public par défaut (ou alors tous ses arguments doivent avoir une valeur par défaut) ;

  • L'objet doit posséder un constructeur de copie public ;

  • L'objet doit posséder un destructeur public.

Il n'est pas obligatoire de définir soi-même ces fonctions puisque celles fournies par défaut conviennent très bien (c'est notamment utile pour les structures).

À partir du moment où ces trois conditions sont respectées, l'objet est prêt à être sérialisé. Pour faire cela, il suffit d'appeler la macro Q_DECLARE_METATYPE de la manière qui suit (dans le cas où on cherche à rendre sérialisable un objet MaClasse) :

Q_DECLARE_METATYPE(MaClasse)

Ce morceau de code est à placer hors de tout objet et de toute fonction. On le met, la plupart du temps, à la suite de la déclaration de l'objet concernée.

Tout à l'heure, tu as dit qu'il était possible de rendre sérialisable un objet qui appartient à une bibliothèque (dont le code source est inaccessible). Je le met où, ce code, alors ? o_O

L'endroit n'a pas une grande importance, tant que ce code précède toutes les fonctions qui vont manipuler l'objet. En général, on le place dans le ou les fichier(s) qui inclue(nt) la librairie en question, juste après l'inclusion :

Inclusion.h

#ifndef INCLUDE_H #define INCLUDE_H // Voici un exemple avec la structure SDL_Event de la bibliothèque SDL #include <QVariant> #include <SDL.h> Q_DECLARE_METATYPE(SDL_Event); #endif

Notez que j'ai appelé la macro après avoir inclus QVariant, ce qui est parfaitement logique.
Pour ceux qui on du mal à saisir, jetez un coup d'oeil au TP-exemple à la fin de ce tutoriel.

Le stockage avec QDataStream

Pourquoi parles-tu de stocker avec QDataStream ? QVariant ne suffit-il pas ?

Non. QVariant est une classe très puissante, mais elle ne peut pas tout faire non plus. Cette partie est là pour résoudre un problème qui apparaît lorsqu'on tente d'enregistrer un objet sérialisé dans un fichier (par exemple).
En effet, jusqu'à maintenant, on s'est contenté de dire à Qt que tel ou tel objet pouvait être sérialisé. Ces déclarations suffisaient totalement à QVariant pour gérer les objets concernés. Mais dans le cadre de l'enregistrement (en général dans un fichier) de l'objet, QVariant a besoin de savoir quelles données et surtout dans quel ordre ces données de l'objet doivent être stockées.

Contrairement à Boost qui impose de créer une fonction serialise dans l'objet (ce qui peut se révéler réducteur), Qt profite du fait qu'on peut sur-définir les opérateurs. Dans le cas de la sérialisation, ce sont les opérateurs de flux ("<<" et ">>") vers QDataStream qui ont été choisis.

Les opérateurs de flux vers QDataStream

La classe QDataStream est là pour fournir un moyen de stocker des suites d'octets, généralement dans un fichier. Avant de passer à la sur-définition des opérateurs de flux manquants, je vais vous expliquer pourquoi est-ce qu'il sera très simple de les mettre en œuvre.

D'origine, QDataStream possède de nombreux opérateurs de flux déjà définis pour les classes de Qt utiles dans ce contexte (la liste complète est visible ici : Format des opérateurs de QDatastream). Puisque la plupart des objets que l'on crée ou rencontre sont majoritairement constitués de ces types, il suffit de réemployer ces opérateurs.

On va donc réaliser les opérateurs "<<" et ">>" vers QDataStream. Voici leur prototype (à placer en-dehors de la déclaration de l'objet concerné) :

QDataStream & operator << (QDataStream & out, const MaClasse & Valeur); QDataStream & operator >> (QDataStream & in, MaClasse & Valeur);

Comme la plupart des opérateurs de flux, ils renvoient le QDataStream donné en paramètre, afin de permettre l'enchaînement des opérateurs.
La technique à employer est simple : pour chaque variable membre (que vous voulez stocker) de l'objet, envoyez celle-ci vers le QDataStream (ou bien sortez-l'en).
Ainsi que vous l'avez peut-être vu dans la liste des opérateurs de flux de QDataStream, il est possible d'envoyer directement des QList, QMap et autres conteneurs au QDataStream. La seule condition est que le type du conteneur ait des opérateurs de flux vers QDataStream.

Les valeurs à stocker doivent être envoyées et sorties d'un QDataStream dans le même ordre, ou bien elles ne correspondront plus.

Voici un exemple, où nom est une QString et numero_rue un entier :

QDataStream & operator << (QDataStream & out, const MaClasse & Valeur) { out << Valeur.nom << static_cast<quint16>(Valeur.numero_rue); return out; }

Extrêmement difficile, n'est-ce pas ? :-°

Pour ceux qui veulent être véritablement portables et durables dans le temps, vous devriez consulter les informations relatives à la version du QDataStream. Tout y est très bien expliqué.

Pour terminer, il reste deux fonctions de la classe QMetaType à appeler pour indiquer à QVariant que les opérateurs de flux ont été définis (il ne le devine pas tout seul o_O). Pour des raisons pratiques, je les place généralement dans une fonction initMaClasseSystem, que j'appelle dans le "main".
Les voici :

  • qRegisterMetaTypeStreamOperators : c'est la fonction qui finit de déclarer pour QVariant les opérateurs de flux (facile à deviner vu le nom). On l'utilise comme suit : qRegisterMetaTypeStreamOperators<MaClasse>("MaClasse");

  • qMetaTypeId : elle n'est pas obligatoire, mais très utile : en effet, dans le cas où le Q_DECLARE_METATYPE échoue (conditions non respectées, par exemple), la compilation échoue elle aussi (ne vous étonnez pas si le compilateur vous envoie des erreurs concernant le code source de Qt). Vous pouvez l'utiliser ainsi : qMetaTypeId<MaClasse>();

Comme je le disais ci-dessus, il suffit de les mettre dans une fonction d'initialisation par exemple, et d'appeler celle-ci dans le main (un seul appel suffit).

TP-exemple : gérer un contact

Que ce soit pour tester vos connaissances fraîchement acquises ou pour comprendre un peu mieux ce qui vient d'être dit, voici un TP expliqué pas à pas pour vous guider dans la sérialisation complète d'une classe représentant un contact.

La classe Contact

La classe Contact comporte cinq propriétés : le numéro de la rue, l'adresse, le nom, la date d'anniversaire et la liste des sites web du contact. Chacune des propriétés a son propre type, afin de bien mettre en évidence la puissance de ce que je vous explique. En outre, les sites webs sont représentés par une structure incluant le nom et l'adresse du site web.
Le but n'est pas de travailler sur la réalisation de la classe Contact elle-même, aussi je vous donne directement son code :

Contact.h

#ifndef CONTACT_H #define CONTACT_H #include <QUrl> #include <QString> #include <QList> #include <QDate> class Contact // Représentation d'un Contact { public: struct SiteWeb { SiteWeb (const QString Nom = "", const QUrl Adr = ""); QString name; QUrl adresse; }; typedef QList<SiteWeb> SiteWebList; Contact (const quint16 NumeroMaison = 0, const QString Adresse = "", const QString Nom = "", const QDate Aniv = QDate(), const SiteWebList Sites = SiteWebList()); // Constructeur par défaut public Contact (const Contact & Copie); // Constructeur de copie public ~Contact (); // Destructeur public void afficher () const; // Affiche les informations du contact...

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Stocker et sérialiser des objets avec Qt

Prix sur demande