Ok! Je vais chercher de ce coté là alors.
Encore une fois, merci ;)
Version imprimable
Ok! Je vais chercher de ce coté là alors.
Encore une fois, merci ;)
Les QTimer fonctionnent bien, a priori exactement ce que je recherchais.
Après différents tests annexes, je pense pouvoir me ré-atteler au projet!
Il y a quelques temps (voir page 2):
J'ai pour le moment une fenêtre toute simple, avec un bouton qui appelle le module principal qui devra gérer la succession des différents modules.
J'ai testé cela:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 #include "FenPrincipale.hpp" FenPrincipale::FenPrincipale() { this->resize(200,100); go = new QPushButton("Go", this); layout = new QVBoxLayout; layout->addWidget(go); setLayout(layout); connect(go, SIGNAL(clicked()), this, SLOT(clicGo())); } void FenPrincipale::clicGo() { QMessageBox::information(this, "Info", "Début du Programme Principal"); Programme_Principal prgm; prgm.execute(); QMessageBox::information(this, "Info", "Fin du Programme Principal"); }
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 #ifndef GLOBALES_H #define GLOBALES_H #include <string> namespace Globales { struct Variables { std::string test; std::string essai; Variables() : test("initialisation de test"), essai("initialisation de essai"){} }; } //namespace Globales #endif // GLOBALES_H
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 #ifndef PROGRAMME_PRINCIPAL_H #define PROGRAMME_PRINCIPAL_H #include "modele/Globales.hpp" struct Globales::Variables; class Programme_Principal { public: Programme_Principal(Globales::Variables & Donnees); void execute(); private: Globales::Variables & donnees; }; #endif // PROGRAMME_PRINCIPAL_H
En somme, le programme n'est sensé rien faire de bien "visuel", mais c'était déjà pour démarrer.Code:
1
2
3
4
5
6
7 #include "Programme_Principal.hpp" Programme_Principal::Programme_Principal(Globales::Variables & Donnees):donnees(Donnees){} void Programme_Principal::execute() { }
A l'exécution, sous Qt Creator, j'ai 2 problèmes:
1- ...Programme_Principal.hpp:6: avertissement : declaration 'struct Globales::Variables' does not declare anything
2- ...FenPrincipale.cpp:16: erreur : no matching function for call to 'Programme_Principal:: Programme_Principal()'
Le premier n'est pas bloquant a priori, mais m'intrigue tout de même. J'ai bien défini la structure avant la classe et suivi les conseils de koala01, mais il doit y avoir quelque chose qui m'échappe.
La seconde est logique, puisque je ne suis pas la définition du constructeur, mais... dans FenPrincipale, pas de structure créée ni initialisée, cela se fait dans Programme_Principal, donc comment passer une référence?...
Faut-il créer la structure encore avant? Pour la passer dans le constructeur de Programme_Principal?
Je trouvais intéressant de ne créer la structure qu'une fois dans Programme_Principal, car c'est de lui que tout partira. Donc, en ayant en membre de la classe la structure, il serait facile de la passer aux modules suivants.
Dois-je alors ne pas déclarer la structure avant, ne rien mettre en argument dans le constructeur et créer la structure à l'intérieur de ce dernier pour initialiser le membre?
C'est parce que la ligne
tente de faire une déclation anticipée de la structure Variables se trouvant dans l'espace de noms Globales, alors que... la dite structure est déjà définie dans le fichier globales.h, qui est inclu juste avant la déclaration anticipée ;)Code:struct Globales::Variables;
[quote]
Les deux possibilités sont valables:Citation:
La seconde est logique, puisque je ne suis pas la définition du constructeur, mais... dans FenPrincipale, pas de structure créée ni initialisée, cela se fait dans Programme_Principal, donc comment passer une référence?...
Faut-il créer la structure encore avant? Pour la passer dans le constructeur de Programme_Principal?
Je trouvais intéressant de ne créer la structure qu'une fois dans Programme_Principal, car c'est de lui que tout partira. Donc, en ayant en membre de la classe la structure, il serait facile de la passer aux modules suivants.
Dois-je alors ne pas déclarer la structure avant, ne rien mettre en argument dans le constructeur et créer la structure à l'intérieur de ce dernier pour initialiser le membre?
Soit tu crées une variable de type Globales::Variables directement dans main, avant de créer ta variable Program_Principal, et tu transmet la variable au constructeur tel qu'il est (après avoir initialisé la dite variable ;) )
Soit tu supprimes la référence du constructeur, et tu transformes ta référence membre Globales::Variables & donnees; en un membre "simple ( Globales::Variables donnees;), et, bien sur, il faudra penser à faire en sorte que la variable soit correctement initiailsée avant tout usage ;)
D'accord, donc c'est soit l'une, soit l'autre, mais pas les deux.
Laquelle privilégier alors?
L'inclusion du fichier ou la déclaration struct?
Par contre dans le .cpp il faut bien remettre l'inclusion?
Je vais prendre la seconde pour ce tout premier module je pense. Comme ca "j'initialise" le tout au début.
Par contre, pour la suite, dois-je faire comme tu m'as conseillé, à savoir passer cette structure en référence à chaque constructeur des classes qui en ont besoin? Avec cela pourrai-je modifier la structure depuis l'extérieur? (permis par la référence?) Dois-je placer la "structure membre" dans la partie publique? Ou dois-je écrire des accesseurs et mutateurs?
Je n'ai pas oublié ce que tu avais dit par rapport aux portées des structures et des classes ainsi que sur les références ^^, mais comme là je mélange le tout... ca fait une mixture pour le moment un peu trouble ;)
Et pour finir:
Comment implémenter le constructeur avec la structure?
J'ai comme unique membre de Programme_Principal "donnees" du type de la structure.
Quand je fais:
Le compilateur ne me renvoie pas d'erreur mais il souligne donnees comme variable inutilisée. Dois-je m'en soucier ou est-ce la bonne méthode?Code:
1
2
3
4 Programme_Principal::Programme_Principal() { Globales::Variables donnees; }
Utilises la déclaration anticipée chaque fois que tu le peux (c'est à dire : chaque fois qu'il te suffit de connaitre le nom d'une classe ou d'une structure, mais que tu n'essayes pas d'accéder à son conenu dans le fichier dans lelquel se trouve la déclaration anticipée) et l'inclusion du fichier chaque fois que tu le dois (comprends : chaque fois que tu ne déclares pas une référence ou un pointeur et / ou chaque fois que tu vas tenter d'accéder au contenu d'une structure ou d'une classe)
Si le fichier n'est pas déjà inclus (même de manière indirecte), oui, en effetCitation:
Par contre dans le .cpp il faut bien remettre l'inclusion?
C'est un choix ;)Citation:
Je vais prendre la seconde pour ce tout premier module je pense. Comme ca "j'initialise" le tout au début.
Normalement, tu ne devrais pas...Citation:
Par contre, pour la suite, dois-je faire comme tu m'as conseillé, à savoir passer cette structure en référence à chaque constructeur des classes qui en ont besoin? Avec cela pourrai-je modifier la structure depuis l'extérieur?
C'est à l'intérieur de tes modules que les modifications devraient etre apportées, et tu ne devrais donc pas (afin de respecter la loi demeter) essayer d'accéder à ta structure en elle-même ;)
Tu peux le faire, dans le sens où ce n'est pas interdit par le langage...Citation:
(permis par la référence?)
Par contre, tu ne devrais pas le faire dans le sens où il n'est jamais bon d'exposer plus que nécessaire un "détail d'implémentation.
Normalement, ce devrait etre les fonctions membres de tes différents modules qui s'occupent de manipuler les données comprises dans Globales::Variables ;)Le mieux encore serait de placer ta structure dans une accessibilité privée et d'éviter le recours aux mutateurs / acesseurs :)Citation:
Dois-je placer la "structure membre" dans la partie publique? Ou dois-je écrire des accesseurs et mutateurs?
Ca se comprend ;)Citation:
Je n'ai pas oublié ce que tu avais dit par rapport aux portées des structures et des classes ainsi que sur les références ^^, mais comme là je mélange le tout... ca fait une mixture pour le moment un peu trouble ;)
Attention, là, tu déclares une variable de type Globales::Variables qui ne sera utilisable que dans le constructeur, et qui cache le membre de meme nom ;)Citation:
Et pour finir:
Comment implémenter le constructeur avec la structure?
J'ai comme unique membre de Programme_Principal "donnees" du type de la structure.
Quand je fais:
Le compilateur ne me renvoie pas d'erreur mais il souligne donnees comme variable inutilisée. Dois-je m'en soucier ou est-ce la bonne méthode?Code:
1
2
3
4 Programme_Principal::Programme_Principal() { Globales::Variables donnees; }
Et, comme tu n'en fais rien, il n'est pas vraiment étonnant que ton EDI t'indique une erreur potentielle (pourquoi créer quelque chose que tu n'utilises plas :question: :D)
Ceci dit, le mieux reste toujours d'utilier les liste d'initialisation ;)
Oui c'est à cela que je pensais. Et donc en passant par référence la structure quand il le faut, ces fonctions membres (ou fonctions de namespaces) pourront modifier le contenu de la structure?
J'ai pensé aux listes d'initialisation, mais tous les exemples que j'ai vu partent de types simples. Puisque ma structure n'est pas créée antérieurement, je ne peux pas la passer dans le constructeur, ainsi, comment faire une liste d'initialisation sans avoir l'objet à mettre dans la variable?
En faisant:
?Code:
1
2 Programme_Principal::Programme_Principal():donnees(Globales::Variables()) {}
[quote=Gm7468;6712410]Oui c'est à cela que je pensais. Et donc en passant par référence la structure quand il le faut, ces fonctions membres (ou fonctions de namespaces) pourront modifier le contenu de la structure?
[QUOTE]Oui, tout à fait ;)
Si ta structure dispose d'un constructeur par défaut (ce qui est le cas si tu ne définis pas toi même un constructeur OU si tu définis un constructeur ne prenant aucun argument ;)), tu peux éviter le recours à la liste d'initialisation, les membres d'une classe (ou d'une structure) étant automatiquement construits dans l'ordre de leur déclaration (au niveau du constructeur) et automatiquement détruit dans l'ordre inverse de leur déclaration dans le destructeur ;)Citation:
J'ai pensé aux listes d'initialisation, mais tous les exemples que j'ai vu partent de types simples. Puisque ma structure n'est pas créée antérieurement, je ne peux pas la passer dans le constructeur, ainsi, comment faire une liste d'initialisation sans avoir l'objet à mettre dans la variable?
En faisant:
?Code:
1
2 Programme_Principal::Programme_Principal():donnees(Globales::Variables()) {}
Ok, je n'avais pas essayé cette possibilité, qui me paraissait "trop simple", et elle fonctionne.
J'ai un constructeur par défaut, qui "initialise" une partie des membres de la structure, et en ne mettant rien pour le constructeur du Programme_Principal (ni liste ni implémentation dans les {}), ca a l'air de fonctionner.
Merci ;)
à bientôt pour de nouvelles questions ^^
Une petite formalité, pour être sur que mon code soit "propre" (quand on en arrive à ces considérations là c'est que ca commence à être assez bien ^^).
Par exemple dans cette fonction toute simple de calcul de la distance d'un point à un plan. Que dois-je déclarer comme constant?Code:
1
2
3
4
5
6
7 double const CalculDistancePointPlan(VPoint const & point, double const & a, double const & b, double const & c, double const & d) { double dist; dist = (a*point.getXPoint() + b*point.getYPoint() + c*point.getZPoint() + d)/sqrt(a*a+b*b+c*c); return dist; }
Là j'ai tout mis... mais à mon avis... je dirais qu'une solution suffirait, non?
- Soit déclarer la fonction comme const (double const CalculDistancePointPlan...), et pas les références vers les arguments.
- Soit déclarer les arguments en références constantes ((VPoint const & point, double const & a, double const & b, double const & c, double const & d)) mais pas la fonction.
Par contre, bien entendu, pour une fonction qui modifie l'un de ses arguments, seule la seconde possibilité serait acceptable.
J'en profite pour redemander confirmation, j'ai tout passé par référence, mais seule la référence vers l'objet VPoint serait nécessaire, c'est bien ça? Car pour les types courants, cela n'apporte rien de plus que la copie?
Quelle solution privilégier alors pour ces types? Les références ou vraiment pas?
EDIT:
J'ai un nouveau problème, je pense qu'il est du aux namespaces que j'utilise, mais je n'arrive pas à le résoudre...
Voici une partie des codes:
Fichier Initialisation.hpp
Fichier Initialisation.cppCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 #ifndef INITIALISATION_H_INCLUDED #define INITIALISATION_H_INCLUDED ... #include "modele/Globales.hpp" ... #include "controleur/Transversaux.hpp" ... namespace Initialisation { void initialisation(Globales::Variables & datas); (...) } //namespace Initialisation #endif // INITIALISATION_H_INCLUDED
Fichier Transversaux.hppCode:
1
2
3
4
5
6
7
8
9
10
11
12
13 #include "Initialisation.hpp" namespace Initialisation { // Module Initialisation void initialisation(Globales::Variables & datas) { (...) General::Transversaux::AlerteMaintenance(datas, "0.4"); (...) } } //namespace Initialisation
Si je commente l'appel à la fonction (General::Transversaux::Alerte...), tout fonctionne. Par contre, si je l'active, lors de la compilation j'ai une erreur:Code:
1
2
3
4
5
6
7
8
9
10 (...) namespace General { namespace Transversaux { (...) void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement = "", int const & numPoint = 0); (...) } //namespace Transversaux } //namespace General
\Initialisation.cpp:48: erreur : undefined reference to `General::Transversaux::AlerteMaintenance(Globales::Variables&, QString const&, QString const&, int const&)'
D'où cela peut-il venir?
J'ai inclus tous les fichiers nécessaires et je nomme correctement les namespaces...
Pour ce qui est des types primitifs (double, par exemple), si les modifications qu'ils pourraient éventuellement subire dans une fonction ne doivent pas être répercutées dans la fonction appelante, tu peux parfaitement les passer simplement par valeur.
Il n'y a, en effet, aucun intérêt à passer des types primitifs par référence, surtout si les modifications qu'ils subissent dans une fonction ne doivent pas être répercutées dans la fonction appelante ;)
Cela sous entend que, si la fonction ne modifie pas l'objet au départ duquel elle est appelée, elle reste susceptible de modifier les arguments qu'elle reçoit... Est ce ce qui t'intéresse :question:Citation:
- Soit déclarer la fonction comme const (double const CalculDistancePointPlan...), et pas les références vers les arguments.
Ce qui sous entend que la fonction modifie l'objet au départ duquel elle est appelée (et ne peut donc pas être appelée depuis un objet constant), mais ne modifie pas les arguments qu'elle reçoit... Encore une fois, est-ce ce qui t'intéresse :question:Citation:
- Soit déclarer les arguments en références constantes ((VPoint const & point, double const & a, double const & b, double const & c, double const & d)) mais pas la fonction.
ou la solution d'une fonction non constante et d'un argument (celui qui doit etre modifié ;) ) passé par référence non constante, si la fonction modifie l'objet depuis lequel elle est appelée ;)Citation:
Par contre, bien entendu, pour une fonction qui modifie l'un de ses arguments, seule la seconde possibilité serait acceptable.
En effet ;)Citation:
J'en profite pour redemander confirmation, j'ai tout passé par référence, mais seule la référence vers l'objet VPoint serait nécessaire, c'est bien ça? Car pour les types courants, cela n'apporte rien de plus que la copie?
généralement le passage par valeur plutot que par référence...Citation:
Quelle solution privilégier alors pour ces types? Les références ou vraiment pas?
Sauf si la fonction doit modifier la valeur de l'argument et que cette modification doit être répercutée dans la fonction appelante, toujours ;)
As tu bel et bien l'implémentation de AlerteMaintenance dans un fichier *.cpp, est il correctement compilé et fait il partie de ton projet :question:Citation:
EDIT:
J'ai un nouveau problème, je pense qu'il est du aux namespaces que j'utilise, mais je n'arrive pas à le résoudre...
Voici une partie des codes:
Fichier Initialisation.hpp
Fichier Initialisation.cppCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 #ifndef INITIALISATION_H_INCLUDED #define INITIALISATION_H_INCLUDED ... #include "modele/Globales.hpp" ... #include "controleur/Transversaux.hpp" ... namespace Initialisation { void initialisation(Globales::Variables & datas); (...) } //namespace Initialisation #endif // INITIALISATION_H_INCLUDED
Fichier Transversaux.hppCode:
1
2
3
4
5
6
7
8
9
10
11
12
13 #include "Initialisation.hpp" namespace Initialisation { // Module Initialisation void initialisation(Globales::Variables & datas) { (...) General::Transversaux::AlerteMaintenance(datas, "0.4"); (...) } } //namespace Initialisation
Si je commente l'appel à la fonction (General::Transversaux::Alerte...), tout fonctionne. Par contre, si je l'active, lors de la compilation j'ai une erreur:Code:
1
2
3
4
5
6
7
8
9
10 (...) namespace General { namespace Transversaux { (...) void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement = "", int const & numPoint = 0); (...) } //namespace Transversaux } //namespace General
\Initialisation.cpp:48: erreur : undefined reference to `General::Transversaux::AlerteMaintenance(Globales::Variables&, QString const&, QString const&, int const&)'
D'où cela peut-il venir?
J'ai inclus tous les fichiers nécessaires et je nomme correctement les namespaces...
Je crois que je m'embrouille quand même un peu avec toutes ces notions...
:question: - Une fonction déclarée comme constante ne modifie pas l'objet duquel elle est appelée, mais cela ne suppose rien sur ses arguments?
:question: - Pour les types courants en argument, on utilise donc soit
si l'argument n'a pas à être modifé;Code:type const argument
soit
s'il doit être modifié.Code:type & argument
Mais ce que je proposais:
n'a donc aucun sens, puisque je passais en référence (donc pour modification) un objet (de type courant) constant?Code:type const & argument
oui, dans Transversaux.cpp, qui inclut Transversaux.hpp, fonction dans le namespace General::Transversaux.
Par contre, le déclaration est
et la définitionCode:void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement = "", int const & numPoint = 0);
(cf. les arguments par défaut, mais je crois qu'on doit bien faire comme cela)Code:void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement, int const numPoint){...}
je ne suis pas sur, mais je dirais a priori oui, car j'utilise d'autres fonctions d'un namespace General::XML placé dans Transversaux.hpp et .cpp et celles-ci fonctionnement.
Le pire, c'est que Qt Creator me propose la fonction quand je tape General::Transversaux, donc il devrait la connaitre...
EDIT: à n'y rien comprendre...
J'ai changé la fonction de place, et je l'ai mise temporairement directement dans le namespace Initialisation:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 #ifndef INITIALISATION_H_INCLUDED #define INITIALISATION_H_INCLUDED #include <QtCore> #include "modele/Globales.hpp" (...) namespace Initialisation { void initialisation(Globales::Variables & datas); void erreurInitiale(Globales::Variables & datas); void chargementXML(QFile & fichierXML, Globales::Variables & datas); void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement = "", int const & numPoint = 0); } //namespace Initialisation #endif // INITIALISATION_H_INCLUDED
Les fonctions erreurInitiale() et chargementXML() fonctionnent, mais la fonction AlerteMaintenance() envoie toujours le problème:Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 #include "Initialisation.hpp" namespace Initialisation { // Module Initialisation void initialisation(Globales::Variables & datas) { erreurInitiale(datas); chargementXML(fReturnCodes, datas); AlerteMaintenance(datas, "0.4"); } // Sous-Module Erreur Initiale void erreurInitiale(Globales::Variables & datas) { //actions... } // Sous-Module Chargement XML void chargementXML(QFile & fichierXML, Globales::Variables & datas) { //actions... } void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement, int const numPoint) { //actions... } } //namespace Initialisation
Initialisation.cpp:47: erreur : undefined reference to `Initialisation::AlerteMaintenance(Globales::Variables&, QString const&, QString const&, int const&)'
Alors que cette fois on est dans le même namespace, avec des fonctions similaires qui fonctionnent!
EDIT 2: résolu mais toujours pas compris...
J'ai tout replacé dans Transversaux.hpp et .cpp.
Et j'ai tout ré-écrit. Par contre cette fois, le compilateur m'a proposé quelque chose d'étrange:
Déclaration de la fonction dans .hpp:
Comme je l'avais définie dans le .cpp précédemment: (qui posait problème)Code:
1
2 void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement = "", int const & numPoint = 0);
Ce que le compilateur m'a proposé: (qui, à présent, fonctionne)Code:
1
2 void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement, int const numPoint)
-> Remarquer le passage de type const & argument à const type &argumentCode:
1
2 void AlerteMaintenance(Globales::Variables &datas, const QString &code, const QString &supplement, const int &numPoint)
(position du const et espace entre le & et l'argument).
Toutes mes autres fonctions ont été faites comme la première solution, qui là n'a pas fonctionné. Pourquoi?
EDIT 3: Compris!
C'est tout simple: j'avais oublié un "&" pour le dernier argument, cette fois ca marche avec la première solution.
Je suis tout de même curieux (cf. titre du post), de savoir quelle solution est à appliquer:
- type const & argument (comme je le fais)
ou
- const type &argument (comme me le propose le compilateur)
Et à ce moment il faudrait appliquer la même chose pour la déclaration (.hpp) et la définition (.cpp)?
Me revoilà pour une nouvelle question, en plus des précédentes ;)
J'ai une classe d'objet qui fait pas mal d'actions plus ou moins complexes et élémentaires. Pour "simplifier" le fonctionnement, je me suis dit qu'il serait judicieux de séparer ces actions élémentaires de la classe. Je les ai donc placées dans un namespace dans d'autres fichiers.
J'ai donc:
Objet.hpp
Objet.cpp
Namespace.hpp
Namespace.cpp
Certaines fonctions du Namespace nécessitent l'objet, j'ai donc inclus Objet.hpp dans le Namespace.hpp. Jusque là aucun problème.
Par contre, ce que j'aimerais faire, c'est utiliser ces fonctions élémentaires de Namespace dans mon objet.
Et donc je devrais aussi inclure Namespace.hpp dans Objet.hpp.
Cependant, lorsque je fais cela, toutes les fonctions de Namespace qui utilisent objet envoient des problèmes:
- variable or field 'Fonction' declared void
- 'Objet' was not declared in this scope
Ce problème vient lorsque j'inclus Namespace.hpp dans Objet.hpp, alors que Objet.hpp est déjà inclus dans Namespace.hpp.
Est-ce que le C++ interdit l'inter-inclusion comme cela?
A ce moment y a-t-il une solution?
Ou dois-je revenir à mon ancienne architecture sans le namespace?
EDIT:
Début de solution trouvée, mais encore une fois, je ne comprends pas bien, si on peut m'éclairer ;)
Dans le Namespace.hpp:
Comme cela, le compilateur ne m'envoie plus de problème.Code:
1
2
3
4
5
6
7
8 #include Objet.hpp class Objet; namespace Namespace{ ... }
Mais pourquoi?
C'est la même histoire qu'avec la structure, mais je ne comprends pas pourquoi le include ne suffit pas, et pourquoi il faut les deux... et uniquement dans le .hpp.
Exactement : elle s'engage uniquement à ne pas modifier l'objet au départ duquel elle est invoquée, mais ne s'engage à rien par rapport aux objets impactés par les arguments qu'elle reçoit ;).
Si les objet impactés par les arguments qu'elle reçoit ne doivent pas être modifiés, il s'agira:
de déclarer l'objet en question comme étant constant
de veiller à ce que les éventuels accesseurs utilisés soient constants (c'est de toutes manières une quasi obligation ;)) et renvoient une référence constante (ou une valeur, qui, du coup, évitera les répercussions sur l'objet d'origine)
==> avec comme résultat que la fonction ne pourra pas recevoir autre chose qu'une valeur (donc, sans répercussion sur l'objet d'origine) ou une référence... constante ;)
Je présumes que tu veux dire "type primitif" (AKA char, short, int, long, long long, leur équivalent unsigned, float, double et long double) :question:Citation:
:question: - Pour les types courants en argument, on utilise donc soit
si l'argument n'a pas à être modifé;Code:type const argument
soit
s'il doit être modifié.Code:type & argument
Mais ce que je proposais:
n'a donc aucun sens, puisque je passais en référence (donc pour modification) un objet (de type courant) constant?Code:type const & argument
Mais oui, en effet, il est purement interdit d'essayer de modifier un objet passé sous forme constante (que ce soit par valeur ou référence constante): le compilateur t'insultera si tu essayes de le faire ;)
Oui, numPoint est déclaré sous la forme d'un int const d'un coté et sous celle d'un int const & de l'autre...Citation:
oui, dans Transversaux.cpp, qui inclut Transversaux.hpp, fonction dans le namespace General::Transversaux.
Par contre, le déclaration est
et la définitionCode:void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement = "", int const & numPoint = 0);
(cf. les arguments par défaut, mais je crois qu'on doit bien faire comme cela)Code:void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement, int const numPoint){...}
L'éditeur de liens cherche donc la fonction prenant une référence constante sur un entier, qu'il ne trouve pas, vu qu'elle est implémentée comme étant une fonction prenant... un entier constant :aie:
EDIT 2: résolu mais toujours pas compris...
J'ai tout replacé dans Transversaux.hpp et .cpp.
Et j'ai tout ré-écrit. Par contre cette fois, le compilateur m'a proposé quelque chose d'étrange:
Déclaration de la fonction dans .hpp:
Comme je l'avais définie dans le .cpp précédemment: (qui posait problème)Code:
1
2 void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement = "", int const & numPoint = 0);
Ce que le compilateur m'a proposé: (qui, à présent, fonctionne)Code:
1
2 void AlerteMaintenance(Globales::Variables & datas, QString const & code, QString const & supplement, int const numPoint)
-> Remarquer le passage de type const & argument à const type &argumentCode:
1
2 void AlerteMaintenance(Globales::Variables &datas, const QString &code, const QString &supplement, const int &numPoint)
(position du const et espace entre le & et l'argument).
Toutes mes autres fonctions ont été faites comme la première solution, qui là n'a pas fonctionné. Pourquoi?
La norme précise que le mot clé const s'applique à ce qui se trouve à sa gauche, sauf s'il n'y a rien à gauche, et dans ce cas, il s'applique à ce qui se trouve à sa droite.Citation:
EDIT 3: Compris!
C'est tout simple: j'avais oublié un "&" pour le dernier argument, cette fois ca marche avec la première solution.
Je suis tout de même curieux (cf. titre du post), de savoir quelle solution est à appliquer:
- type const & argument (comme je le fais)
ou
- const type &argument (comme me le propose le compilateur)
Les deux versions sont donc totalement valide, à la seule différence que la pemière utilise la "règle générale" et la seconde utilise "l'exception à la règle".
Certains (comme moi, mais je suis peut etre une exception ;) ) préféreront utiliser la règle générale, et d'autres l'exception ;)
Du point de vue du langage en lui-même, tu peux parfaitement utiliser l'un dans la déclaration et l'autre dans la définition, voir, meme, décider d'utiliser l'un ou l'autre selon "l'air du temps" ou la direction du vent ;)Citation:
Et à ce moment il faudrait appliquer la même chose pour la déclaration (.hpp) et la définition (.cpp)?
Cependant, il faut bien etre conscient que l'idéal est toujours de garder une certaine homogénéité, au minimum au niveau d'un projet donné;)
Merci 8-)
Mais donc, cela veut dire que pour déclarer quelque chose en const, la norme voudrait que le mot clé soit toujours tout à droite?
C'est-à-dire:
pour une fonction:
et pour une variable:Code:double fonction() const;
Ou bien seul le type doit être déclaré constant?Code:double variable const;
Code:double const fonction();
Dans ce second cas, est-ce la règle de la norme ou l'exception qui est appliquée?Code:double const variable;
Puisqu'il y a quelque chose à gauche, c'est le type, et quelque chose à droite, le nom, mais comme la gauche n'est pas vide, const ne s'applique pas au nom...
Donc... première ou seconde solution?
Sachant que comme tu l'as dit, l'essentiel est d'être cohérent, et pour le moment j'ai tout fait comme la seconde solution, donc c'est cohérent. Mais l'important pour moi est, d'une part de comprendre, et d'autre part que ce soit juste.
En plus de la question précédente, voici une nouvelle question.
Elle a toujours attrait au mécanisme de signaux et slots. C'est vrai que j'ai à présent intégré le système de déclenchement action/réaction, mais je souhaiterais aller plus loin, si cela est possible.
Suivant les conseils de koala01, j'ai le fonctionnement suivant, par exemple pour le déplacement du bras:
Lorsque le bras a à être déplacé, on appelle la fonction deplaceBras(), celle-ci s'occupe de lancer la requête de placement par le port COM, et de démarrer le timer de contrôle.
2 possibilités sont alors envisagées: soit on arrive à la fin du timer (il y a eu un problème lors du placement), soit le placement s'est bien effectué, on a reçu la réponse du bras disant "je suis placé".
L'une ou l'autre des solutions aboutit donc sur une dernière fonction qui gère le résultat, par exemple 0 si tout a bien été, 1 si le timer est arrivé au bout.
Et voici donc ma question:
N'est-il pas possible de concentrer le tout dans une seule fonction?
A savoir, par exemple:
C'est-à-dire, n'y a-t-il pas moyen d'englober le système de signaux/slots à l'intérieur même d'une fonction, pour que, à partir de l'extérieur, on ait juste à appeler placementDuBras(), sans se soucier des signaux et slots émis/reçus et du fonctionnement interne?Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 int placementDuBras(){ deplaceBras(); //attente du signal?... return gestionResultat(reponse); } void deplaceBras(){ timer.start(); //envoie requête de placement par port COM } int gestionResultat(string reponse){ if (reponse == "fin timer") { return 1;} else{ return 0;} }
J'ai pensé à un timer "interne", qui permet de vérifier à intervalles réguliers l'état d'une variable qui sera modifiée lors de l'émission d'une des solutions (fin timer ou réception réponse). On attendrait donc effectivement tant qu'un des deux évènements n'ait pas eu lieu. Mais je trouve cette solution un peu archaïque et pas très optimisée...
Je m'explique sur cette question:
Toujours dans mon projet conséquent, j'ai à commander des actions par communications sur un port COM.
J'ai donc une classe spéciale héritant de QextSerialPort, une classe spécifique développée à coté de Qt pour gérer les interfaces série. Cette classe contient différentes fonctions séparées en éléments simples:
- Création de la requête (chaine de caractères)
- Envoi de la requête (émission de la chaine sur le port COM)
- Réception de données sur le port COM (chaine de caractères)
- Traitement de la réponse (types divers)
J'ai donc 2 possibilités (en tout cas d'après ce que j'envisage) pour faire les actions que je souhaite:
1* soit je passe par une succession de signaux/slots
2* soit je fais des fonctions spécifiques qui englobent ces signaux/slots
Le problème avec la solution 1*, c'est que ca me fait une quantité gigantesque de signaux et slots à gérer ou alors de paramètres pour les gérer. Cela est du au fait que la communication COM sert à beaucoup de choses, et est utilisée depuis plusieurs endroits (modules). La réception et le traitement d'une même réponse peuvent donc, suivant les cas, appeler la suite d'une fonction ou d'une autre.
J'ai donc imaginé (je l'espère), que la solution 2* serait plus simple à utiliser.
Par exemple pour un simple test de la connexion COM, je devrais:
en solution 1*:
Envoyer la requête, et spécifier quelque part l'endroit d'où elle est envoyée. Lancer le timer. Gérer la réponse, soit fin timer, soit réponse reçue. Puis gérer l'appelant d'après ce qui a été spécifié tout à l'heure, puis émettre un signal, fonction de l'appelant, pour que tel ou tel appelant implique tel ou tel signal connecté à telle ou telle suite.
en solution 2*:
J'aurais une fonction pour chaque action que je souhaite réaliser. Par exemple testConnexion(), qui renverrait un entier d'après la réponse. Cette fonction en elle-même ferait toute les actions décrites en 1*, mais sans se soucier de l'appelant, puisqu'elle s'exécuterait directement dans cet appelant.
Cela me permettrait donc d'une part de simplifier la gestion des signaux/slots en évitant la foultitude de possibilités, et d'autre part, de structurer les actions réalisées de manière propre.
Ainsi, pour des successions d'actions, je pourrais faire:
directement là où j'en ait besoin;Code:
1
2
3 action1(); action2(); action3();
au lieu de:
et ce pour chaque action envisagée, avec un appelant qui hérite lui aussi de QObject pour pouvoir intégrer les signaux émis par l'objet de port COM.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 requete1(); //signal de réception connecté à traitement traitement(id){ if(...){emit resultat1;} else if(...){emit resultat2;} else if(...){emit resultat3;} } //signaux résultat 1, 2 et 3 connectés à resultat(id, appelant) resultat(id, appelant){ if(appelant == ...){... emit retourappelant1;} else if(appelant == ...){ ... emit retourappelant2;} }
Pour info, j'ai trouvé une classe extrêmement intéressante, à vrai dire exactement ce que je recherchais: QxtSignalWaiter. Elle permet de faire ce que j'ai matérialisé dans le code par //attente du signal?....
On connecte l'objet à un signal, et un timer est intégré. Dans le code, on lance le timer de l'objet, et 2 possibilités de sortie sont présentes, soit la fin du timer, soit la réception du signal.
Le post précédent est donc résolu, je vais faire mes petites fonctions qui regroupent le tout, avec le fonctionnement signaux/slots mais à l'intérieur même de la fonction, ce qui est rendu possible par cette nouvelle classe.
Une nouvelle question me vient cependant à l'esprit:
Le code source n'est a priori plus maintenu, mais cela pose-t-il un problème?
La classe fonctionne, le code est en "free software", et disponible sur internet. Au-delà de cela je n'ai rien à me soucier?
La question du post d'avant est toujours sans éclaircissement ;)
Et cette fois uniquement une confirmation.
J'ai quelque part dans un namespace Namespace une fonction:
Dans la classe Objet, j'aimerais utiliser cette fonction, j'ai donc une seconde fonction:Code:void fonctionUne(Objet & obj);
Avec le this tout simple, ca ne fonctionnait pas, normal, puisque la fonction demande une référence vers un objet de type Objet et je lui envoyais un pointeur.Code:
1
2
3
4
5 void fonctionDeux(){ ... Namespace::fonctionUne(*this); ... }
Pouvez-vous me confirmer que le *this est bien ce qu'il faut? (déréférencement du pointeur pour avoir un objet, ok, mais je n'envoie quand même pas une référence...)
Pour les variables et les arguments, c'est le type qui doit etre déclaré constant, pour les fonctions memvre, c'est la fonction ;)
Je répète ce que dit la normeCitation:
Code:double const fonction();
A ton avis, à quoi s'appliquerait le mot clé const ici :question:Citation:
Envoyé par "la norme
C'est bien ca ;)Citation:
Code:double const variable;
cf ma question ci-dessus ;)Citation:
Dans ce second cas, est-ce la règle de la norme ou l'exception qui est appliquée?
Pour être clair :Citation:
Sachant que comme tu l'as dit, l'essentiel est d'être cohérent, et pour le moment j'ai tout fait comme la seconde solution, donc c'est cohérent. Mais l'important pour moi est, d'une part de comprendre, et d'autre part que ce soit juste.
sont acceptésCode:
1
2
3
4
5 const type /* & / * */ variable const type /* & / * */ argument type const /* & / * */ variable type const /* & / * */ argument Type MaClass::fonction() const
s'applique à la valeur de retourCode:Type const /* & / * */ fonction()
D'accord, c'est bien ce qui m'a semblé, donc finalement je fais... n'importe quoi.
Quelle est la différence entre type const fonction();
et type fonction() const; ?
La seconde est lorsque la fonction ne modifie pas l'objet duquel elle est appelée, la première... défini le résultat comme constant?
Mais quel est l'intérêt alors de déclarer une sortie de fonction constante, puisqu'on la met forcément dans une variable qui sera elle modifiée par la suite au besoin?
Donc finalement pour résumer, et je me tiendrai à présent à cela:
Pour les variables et arguments, c'est le type qui doit être déclaré constant (et pas le nom donc), exemple:
Pour les fonctions, c'est toute la fonction, exemple:Code:double const variable;
Cette dernière ligne n'a de sens que pour les fonctions membres.Code:double fonction() const;
Par conséquent, pour des fonctions situées dans un namespace,
est complètement faux;Code:double fonction() const;
est inutile.Code:double const fonction();
C'est bien cela?
C'est bien cela ;)
Si tu renvoies le résultat par valeur, presque aucun (hormis celui de "documenter" le fait que la valeur renvoyée n'est pas sensée être modifiée par la suite).Citation:
Mais quel est l'intérêt alors de déclarer une sortie de fonction constante, puisqu'on la met forcément dans une variable qui sera elle modifiée par la suite au besoin?
Mais, dés que le retour de la fonction est une référence ou un pointeur sur quelque chose, l'intérêt est immense car il implique que tu ne pourras pas modifier le résultat de la fonction, et donc, cela garanti (entre autre) qu'une fonction déclarée constante ne pourra en aucun cas modifier l'objet depuis lequel elle est appelée, même de manière indirecte (par exemple : parce que l'on essayerait de modifier le résultat renvoyé par la fonction ;))
Cela entre, finalement, dans le cadre du stricte respect de la const correctness ;)
T'as tout compris ;) :DCitation:
Donc finalement pour résumer, et je me tiendrai à présent à cela:
Pour les variables et arguments, c'est le type qui doit être déclaré constant (et pas le nom donc), exemple:
Pour les fonctions, c'est toute la fonction, exemple:Code:double const variable;
Cette dernière ligne n'a de sens que pour les fonctions membres.Code:double fonction() const;
Par conséquent, pour des fonctions situées dans un namespace,
est complètement faux;Code:double fonction() const;
Meme pas (cf quelques lignes plus haut), et, surement pas si c'estCitation:
est inutile.Code:double const fonction();
;)Code:UnTypeQuelconque const & fonctions();
Ok d'accord, encore une notion qui s'éclaircit petit à petit :ccool:
Merci à toi ;)
à bientôt
Me revoilà pour une nouvelle question, qui se rapproche de ce cas:
Mais la solution que j'avais trouvée ne fonctionne pas ici, et le problème est plus complexe.
J'ai:
- une classe d'objet: Objet (.hpp et .cpp)
- une structure: Structure (.hpp)
- un namespace pour diverses actions: Namespace (.hpp et .cpp)
La structure Structure a une variable membre de type Objet; et le namespace Namespace a des fonctions qui utilisent la structure Structure.
Jusque là, tout va bien, aucune erreur de compilation, les objets, structures, namespaces et fonctions sont déjà utilisés, tout fonctionne.
Maintenant, pour des questions de fonctionnement de mon programme, je voudrais utiliser une des fonctions de Namespace dans Objet. J'inclus donc dans Objet.hpp le Namespace.hpp. Et là, c'est le drame, avant même de faire quoi que ce soit d'autre, pas d'utilisation de fonction, rien, juste l'include, le compilateur m'envoie des erreurs à la pelle concernant la fonction du namespace, qui, auparavant, fonctionnait.
Pour bien poser le problème, je veux utiliser dans Objet une fonction de Namespace qui utilise Structure, elle même contenant une variable de type Objet. Je ne sais pas si ca pose problème, mais j'ai l'impression que ca vient - au moins en partie - de là.
Voici l'architecture du code:
Structure.hpp
Objet.hppCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 #include "Objet.hpp" namespace Structure { struct Variables { (...) Objet objet; (...) //constructeur de la structure, avec initialisation de certaines variables Variables() { (...) } }; } //namespace Structure
Namespace.hppCode:
1
2
3
4
5
6 //#include "Namespace.hpp" //c'est lorsque cette ligne est décommentée que le compilateur hurle class Objet : public Objetparent { (...) //des fonctions, des slots, des signaux, des variables membres };
Voilà pour le code, et donc quand je compile ca avec Qt Creator, sans même utiliser la fonction, uniquement en faisant le include, j'obtiens plein d'erreurs, notamment:Code:
1
2
3
4
5
6
7
8
9
10
11
12 #include "Structure.hpp" namespace General { namespace Transversaux { //fonction que je souhaite appeler dans Objet void fonction(Structure::Variables & datas, ...); } //namespace Transversaux } //namespace General
- 'Structure' has not been declared (à la ligne de la définition de la fonction dans le namespace, ligne 8 de Namespace.hpp)
- variable or field 'fonction' declared void (à la même ligne)
Je pense que le tout vient d'un problème de la structure, mais son .hpp est bien inclus, j'ai essayé également de rajouter dans Namespace.hpp, au tout début, struct Structure::Variables;, mais ca ne fonctionne quand même pas.
Une solution?
Hello,
Je pense que tu as un problème de références croisées :
(Objet inclut Namespace, et Namespace inclut Struture qui inclut Objet).
Il faudra donc dans un des fichiers d'en tête remplacer l'inclusion par une déclaration anticipée (et non ajouter) :
http://cpp.developpez.com/faq/cpp/?p...erence_croisee
edit:
par exemple, enlever
de Namespace.hpp pour le mettre dans Namespace.cpp, et faire une déclaration anticipée de Structure::Variable dans Namespace.hpp.Code:#include "Structure.hpp"
Merci pour ta réponse.
J'avais pensé à un problème de ce type, mais je ne voyais pas comment appliquer la chose.
Dans:
* Namespace.hpp, j'ai donc supprimé #include "Structure.hpp"
* Namespace.hpp, j'ai ajouté struct Structure::Variables;
* Namespace.cpp, j'ai ajouté #include "Structure.hpp"
Cependant, j'ai toujours des erreurs:
- des warnings au début: declaration 'struct Structure::Variables' does not declare anything
- et des problèmes par la suite: 'Structure' has not been declared, cela est du au fait que le Structure.hpp qui contient ce namespace n'est plus include dans le Namespace.hpp...
Le problème également est que pour la structure, je n'ai qu'un .hpp qui contient le namespace contenant la déclaration et le constructeur car le code est très court. Ainsi, si je supprime dans Structure.hpp le #include Objet.hpp et que je mets à la place class Objet;, le compilateur me dit que field 'objet' [donc l'objet de type Objet contenu dans la structure] has incomplete type...
Je pense que la solution est bonne mais l'application fait défaut.
La déclaration anticipée doit se faire sous la forme :
Il faut bien déclarer Variables dans le namespace Structure, et donc déclarer ce namespace..Code:
1
2
3
4 namespace Structure { struct Variables; }
ta lignene fonctionne pas car le namespace Structure n'a pas été inclus (et pour cause).Code:struct Structure::Variables;
edit
Tu ne peux pas faire une déclaration anticipée de Objet dans Structure, carCitation:
Le problème également est que pour la structure, je n'ai qu'un .hpp qui contient le namespace contenant la déclaration et le constructeur car le code est très court. Ainsi, si je supprime dans Structure.hpp le #include Objet.hpp et que je mets à la place class Objet;, le compilateur me dit que field 'objet' [donc l'objet de type Objet contenu dans la structure] has incomplete type...
or Structure::Variables a un membre de type Objet qui n'est ni un pointeur ni une référence.Citation:
Envoyé par faq c++
Effectivement, je faisais mal la déclaration anticipée. A présent cela fonctionne.
Maintenant une petite question subsidiaire, qui ne devrait pas poser trop de problème:
Comme présenté dans mon premier post, j'ai dans Namespace une fonction fonction qui prend en argument une référence vers la structure Structure.
Est-ce possible d'utiliser cette fonction dans l'objet Objet?
A priori, je dirais oui, mais cela impliquerait que les objets de la classe Objet aient une variable membre contenant une référence vers la structure. Sur le principe, pas de problème.
Mais lors de la création, il faut donc, dans le constructeur de la structure Structure, que je passe au constructeur de l'objet Objet une référence vers la structure elle-même... comment?
Il faut que dans Objet, je crée un mutateur setStruct(Structure::Variables & datas) et qu'il soit appelé dans le constructeur de Structure?
A vrai dire ca me pose un petit problème de conception, que la structure contienne un objet qui contient une référence vers la structure qui le contient... etc... mais bon, tant que c'est possible et que ca fonctionne ;)
Pas de réponse pour la question précédente?
Je me permets tout de même d'en rajouter une, qui est sur un sujet différent: l'héritage.
J'ai créé différentes classes d'objet avec une filiation pas trop trop complexe:
- classe Mere
- classe Fille1
- classe Fille2
- classe Fille1.1
Les classes Fille1 et Fille2 héritent de la classe Mere, la classe Fille1.1 hérite de Fille1.
Jusque là, pas trop de problème.
J'ai créé ensuite des fonctions dans un namespace, hors des classes, qui utilisent ces classes:
* fonction type Calcul(... Fille1 objet ...);
* fonction type Calcul(... Fille2 objet ...);
Les deux fonctions renvoient le même type, ont le même nom (elles sont donc surchargées), et ont les mêmes attributs, à ceci près que l'une nécessite un objet Fille1 et l'autre Fille2. Les calculs à l'intérieur des fonctions sont similaires, mais certains paramètres spéciaux sont pris en compte suivant un ou l'autre des types. Par conséquent, quand j'appelle Calcul(objet);, suivant le type de l'objet, c'est l'une ou l'autre des fonctions qui est utilisée.
Encore jusqu'ici, pas de problème.
Maintenant, dans mon programme, j'ai 2 maps. Une map1 <int, Fille1.1> et une map2 <int, Mere>.
Lorsque j'appelle Calcul(); sur les éléments de map1, le calcul a l'air de fonctionner. En effet, Calcul attend un objet Fille1 ou Fille2, on lui donne un Fille1.1, qui est un Fille1, donc il est content.
Dans map2, lors de sa création, je mets des objets Fille1 et Fille2. Ca ne pose pas de problème puisque Fille1 et 2 sont des objet Mere du fait de l'héritage.
° Première question: est-ce qu'en faisant cela, je perds mes objets? C'est-à-dire, est-ce que mes objets Fille1 et Fille2 sont "dépréciés" en objets Mere pour pouvoir être mis dans map2 qui demande des Mere ou est-ce que leur "structure interne" est conservée?
Je dirais a priori non, car quand j'appelle ma fonction calcul sur map2, le compilateur me renvoie une erreur. En effet, je donne à la fonction des objets Mere, alors qu'il attend Fille1 ou Fille2. Cependant, à l'intérieur de map2, c'est bien des Fille1 et Fille2 que j'ai placé. Je dirais donc qu'ils ont bien été "dépréciés".
° Deuxième question: Comment faire alors pour avoir une map qui contient à la fois des Fille1 et Fille2, sans que ceux-ci perdent leurs particularités?
Vous l'aurez compris, j'ai besoin d'un conteneur qui trie les objets selon un identifiant (le int), mais peu m'importe de savoir que dans le sac ce sont des Fille1 ou des Fille2, l'essentiel est qu'ils soient stockés ensemble.
° Question 2bis: Si c'est possible, alors je vais faire comme vous m'indiquez. Par contre, si cela n'est pas possible, il faudra donc que je sépare mes objets Fille1 et Fille2? Et donc que je perde "l'interclassement" Fille1/2?
Sans trop connaitre le reste du code, je crois qu'une solution serait de s'appuyer sur le polymorphisme, et de ne garder qu'une seule fonction traitant des objets Mere :
Mere devra comporter des méthodes virtuelles pures :Code:* fonction type Calcul( Mere* objet );
Ces méthodes seront implémentées spécifiquement dans chacune des classes filles, donc un appel de paramSpecial1() sur un objet Mere dans Calcul() appellera l'une de ces implémentations spécifiques.Code:
1
2
3
4
5
6
7 class Mere { ... virtual int paramSpecial1() = 0; virtual double paramSpecial2() = 0; ... };
Par rapport à la question 1, la réponse est non, par contre il faut bien veiller à munir Mere d'un destructeur virtuel.
Merci cob59 pour cette réponse.
La solution du polymorphisme a l'air de fonctionner.
Par contre, le fait de mettre ma classe Mere en virtuelle pure posait problème. J'ai donc fait uniquement des méthodes virtuelles (pas pures), renvoyant des valeurs par défaut dans l'implémentation dans Mere, et ré-implémentées correctement quand il le fallait dans les classes filles.
La fonction Calcul, qui appelle un objet Mere, fonctionne à présent, que je lui passe un Fille1 ou un Fille2.
Concernant la question 1, ton non répond à quelle partie? A la partie est-ce qu'en faisant cela, je perds mes objets?
Cela veut donc dire que mes objets sont conservés tels quels, et donc dans la map d'objets Mere j'ai donc bien soit des objets Fille1 soit des Fille2.
A quoi correspond ce "destructeur virtuel"?
Et en ai-je toujours besoin puisque ma classe Mere n'est finalement pas virtuelle pure?
En tout cas merci pour cette réponse, qui a priori a débloqué le problème ;)
Il reste donc interrogation sans réponse...
Oui, c'est obligatoire (1), dés le moment où une classe a vocation à être dérivée, qu'elle soit instanciable ou non, afin de permettre la destruction correcte des classes dérivées.
En effet, tu peux avoir une collection de pointeurs sur la classe mère qui pointent sur des objets du type des classes dérivées.
Si tu veux détruire l'objet au départ de ce pointeur, il faut s'assurer que la destruction soit complete et correcte, ce qui implique que le comportement du destructeur de la classe de base doit s'adapter au type réel.
Le seul moyen d'avoir cette certitude est d'avoir un destructeur virtuel ;)
(1) Il existe une autre possibilité qui consiste à mettre le destructeur dans l'accessibilité protégée, et non virtuel.
Cependant, cela implique que, chaque fois que tu voudras détruire un objet de type dérivé, tu devra le considérer (et le connaitre) comme étant de son type réel, et tu ne pourras pas le considérer comme "étant du type" de la classe de base
Le destructeur par défaut ne suffit-il pas pour cela?
Si mes classes ne contiennent que des variables des types standards, et que je ne crée jamais d'objets avec new, le destructeur par défaut devrait suffir.
Enfin si c'est tout de même obligatoire, seule la classe Mere doit avoir le destructeur et son implémentation?
Je devrais donc placer, dans Mere.hpp:
et dans Mere.cpp:Code:virtual ~Mere();
Mais que mettre dans l'implémentation? (la classe Mere, mais aussi ses Filles, ne contenant que des bool, string, int et double?)Code:~Mere(){}
Imaginons 2 classes Mere et Fillle :
Mere et Fille font respectivement 8 et 12 octets en mémoire :Code:
1
2 class Mere { int attr1; int attr2; }; class Fille : Mere { int attr3; }
Prenons le cas suivant :Citation:
assert ( sizeof(Mere) == 8 );
assert( sizeof(Fille) == 12 );
Que se passe-t-il lors de l'appel du delete ?Citation:
Mere* ptr = new Fille;
... (utilisation "polymorphe" de l'objet pointé par ptr) ...
delete ptr;
Le compilo croit avoir à faire à un pointeur sur un objet Mere, donc appelle Mere::~Mere() sur ptr, ce qui a pour effet de désallouer ses attributs attr1 et attr2.
Mais quid de attr3 ? Qui s'occupe de le désallouer, lui ? En théorie, Fille::~Fille(). Mais comme on a dégradé le type <Fille*> le désignant en un type <Mere*>, plus moyen de désallouer l'objet jusqu'au bout. 12-8=4 octets sont ignorés : on aboutit donc à une fuite mémoire. On pourrait faire delete (Fille*)ptr, ce qui réintroduirait une information sur le type exact de l'objet. Mais il n'est pas certain que cette information sera connue au moment de la désallocation.
Donc on utilise un destructeur virtuel : en pratique, cela consiste à toujours munir un objet Mere (ou dérivé) d'un pointeur vers sa fonction de désallocation la plus apropriée (cf certains tutos, qui te l'expliqueront mieux que moi). Au moment du delete, l'emplacement du destructeur n'est donc pas déduit du type de ptr, mais de sa vtable. Celle-ci nous oriente vers ~Fille() qui supprime attr3 puis, par héritage, nous redirige vers ~Mere().
Il faut procéder de cette manière même si on n'utilise jamais de new et delete?
Si oui, il faut alors que je déclare et implémente des destructeurs virtuels dans toutes mes classes liées à cet héritage?
C'est-à-dire:
Les destructeurs par défaut étant appelés puisque les contenus des classes sont simples et n'ont pas à être détruits "spécifiquement".Code:
1
2
3
4 virtual ~Mere() = default; virtual ~Fille1() = default; virtual ~Fille2() = default; virtual ~Fille1.1() = default;
Ou bien dois-je implémenter "pas à pas" les destructeurs?
Si oui, comment? Détruire chaque variable l'une après l'autre?
J'imagine qu'il y a certains cas où les destructeurs virtuels sont inutiles.Code:Il faut procéder de cette manière même si on n'utilise jamais de new et delete?
Ici, si 'vue' contient un pointeur vers chaque élément de type filleN, alors 'vue' ne détient pas ces éléments ; détruire 'vue' ne détruit pas les objets pointés. Donc on peut se permettre de mettre des destructeurs non-virtuels aux classes FilleN.Code:
1
2
3
4
5 std::vector<Fille1> filles1; std::vector<Fille2> filles2; std::vector<Fille3> filles3; std::vector<Mere*> vue;
Sur les destructeurs :
De même qu'il existe une liste d'initialisation avant le corps du constructeur, qui instancie tous les attributs, on peut se figurer une "liste de finalisation" après le destructeur, qui détruit ces attributs. En appelant ~Mere() sur une instance de Fille, cette "liste" est donc incomplète.
Je suppose que oui.
Mais si Mere n'est pas directement instanciable, pense à mettre ~Mere() en protected pour t'assurer que la destruction d'un objet Fille est bien amorcée depuis ~Fille() et pas depuis ~Mere() (FAQ).
Par contre es-tu sûr de ne jamais utiliser de pointeurs/références ? C'est souvent un passage obligé pour utiliser le polymorphisme. A moins que tu ne passes par un smart_ptr.
Des pointeurs, sur que oui, je n'utilise pas, par contre, des références, si, j'en utilise.
Ces questions sous-entendent que je dois faire les destructeurs? ^^
Pour détruire un entier par exemple, comment procéder?
Par exemple si la classe est:
Le destructeur devra donc détruire entier, nombre et test, mais comment le déclarer et l'implémenter?Code:
1
2
3
4
5
6 ... //attributs int entier; double nombre; bool test;
a priori, je dirais que non, car, même si tu estimes, pour l'instant, n'avoir pas besoin de l'allocation dynamique, tu ne sais jamais dire ce que l'avenir te réserve.
Si, pour une raison ou une autre, tu décides par la suite de créer un objet de type Derive1 ou Derive2 (Derive1 et Derive2 héritant tous deux de Mere) de manière dynamique (par exemple, en réponse à une entrée utilisateur qui permettra de choisir entre les deux), le seul moyen d'y parvenir sera de passer par un pointeur sur la classe mère et par l'allocation dynamique.
Mais, qui dit allocation dynamique dit... libération de la mémoire quand tu n'as plus besoin de l'objet pointé par le pointeur.
Et comme la libération de l'objet peut survenir à peu près n'importe où, et, la loi de murphy aidant, de préférence à un endroit où tu ne disposeras que d'un pointeur sur Mere, si le destructeur de Mere n'est pas virtuel, il n'y aura que les membres de Mere qui seront détruit, et tu te retrouvera donc avec un objet partiellement détruit.
Tout à fait... Tout ce qu'il faut, c'est avoir la certitude que, en détruisant un obet pointé par un pointeur sur la classe mère, le compilateur sache qu'il doit travailler de manière polymorphe, en allant chercher le bon destructeur dans la v-tbl ;)Citation:
Enfin si c'est tout de même obligatoire, seule la classe Mere doit avoir le destructeur et son implémentation?
Je devrais donc placer, dans Mere.hpp:
et dans Mere.cpp:Code:virtual ~Mere();
Code:~Mere(){}
Rien si, au sein de la classe, tu n'utilises pas l'allocation dynamique et les pointeurs.Citation:
Mais que mettre dans l'implémentation? (la classe Mere, mais aussi ses Filles, ne contenant que des bool, string, int et double?)
Par contre, si tu utilises un membre qui est un pointeur sur un objet, il s'agira de s'interroger sur "qui doit prendre la responsabilité" de détruire le membre en question, et il n'est pas exclu qu'un delete sur ce membre soit nécessaire
cf ma réponse précédante
Idéalement, oui, bien que ce ne soit pas obligatoire par la norme.Citation:
Si oui, il faut alors que je déclare et implémente des destructeurs virtuels dans toutes mes classes liées à cet héritage?
Dés qu'une fonction est déclarée virtuelle dans la classe mère, elle est connue comme étant virtuelle dans les classes dérivées, ce qui fait que, d'après la norme, on peut ne pas rappeler le mot clé virtual.
Cependant, il est toujours "de bon ton" de rappeler le mot clé virtual dans les fichiers d'en-tête parce qu'il représentent l'une des sources de documentation les plus importantes que tu aies à ta disposition.
Le fait de rappeler le mot clé virtual pour les fonctions qui sont redéfinies dans les classes dérivées te permettra de retrouver "plus facilement" les fonctions que tu peux redéfinir si, d'aventure, tu décide de dériver une nouvelle classe de l'une des classes enfant ;)
A vrai dire, je ne sais plus ce que dis la norme au sujet des destructeur déclarés defaut :aie:Citation:
C'est-à-dire:
Code:
1
2
3
4 virtual ~Mere() = default; virtual ~Fille1() = default; virtual ~Fille2() = default; virtual ~Fille1.1() = default;
Et je n'oserais pas jurer que ce soit applicable aux destructeurs virtuels (pas plus que je n'oserais jurer le contraire, d'ailleurs ;))
N'oublies pas, en outre, que les destructeur default ont fait leur apparition dans C++11, et que, même si la plupart des compilateur supportent déjà correctement cette nouvelle norme, ce n'est le cas que pour les versions les plus récentes de ceux ci ;)
Comme je l'ai dit dans ma réponse précédente, il suffit souvent de donner une destructeur vide (les membres n'étant pas des pointeurs étant, de toutes façons, détruit automatiquement dans l'ordre inverse de leur déclaration).Citation:
Les destructeurs par défaut étant appelés puisque les contenus des classes sont simples et n'ont pas à être détruits "spécifiquement".
Ou bien dois-je implémenter "pas à pas" les destructeurs?
Si oui, comment? Détruire chaque variable l'une après l'autre?
Le plus important n'est pas de mettre "à tout prix" quelque chose dans le destructeur, mais bien d'éviter que le compilateur ne mette de manière automatique un destructeur par défaut qui serait public et non virtuel ;)
Qu'en est-il de la fonction Calcul citée un peu plus haut ? Elle utilise pourtant bien un pointeur :Je viens de relire un de tes messages et quelque chose m'interpelle :Code:type Calcul( Mere* objet );
Tu es sûr que ta classe Mere est bien abstraite (=> non-instanciable) ? Il ne faudrait pas que tu manipules des objets Mere en pensant faire du polymorphisme...Citation:
Par contre, le fait de mettre ma classe Mere en virtuelle pure posait problème.
Quant aux attributs d'une classe, ils seront tous automatiquement désalloués du moment que tu appelles le destructeur ~T() d'un objet de classe T, et pas celui de son parent. Dans le cas d'un objet alloué sur la pile, tu n'as même pas à t'en préoccuper.
Il faut faire attention aux termes...
Seules les fonctions membres de classes peuvent être virtuelles, et seules les fonctions virtuelles peuvent être "pures".
Le principe d'une fonction virtuelle pure est de se dire
Dans ce cas, on peut décider de ne pas donner d'implémentation de la fonction, mais il faut le signaler à l'éditeur de liens pour qu'il ne s'étonne pas de ne pas trouver le symbole correspondant à la fonction.Citation:
Je sais que la classe de base doit exposer tel ou tel comportement, mais je ne dispose pas, au niveau de la classe de base, des données qui permettent de fournir un comportement cohérent.
On vas donc prévenir le compilateur que l'on ne fournit pas d'implémentation en ajoutant = 0 à la fin de la déclaration de la fonction (dans la classe)
Seulement, le compilateur a horreur du vide, et, comme il se trouve avec un symbole qui n'est "relié à rien", il refusera que l'on crée une instance de la classe de base, simplement, pour éviter les problèmes qui ne manquerait pas de survenir si, d'aventure, on venait à créer un objet dont le type est celui de la classe de base et à appeler cette fonction "qui n'existe pas" dans le code binaire à exécuter.
Comme le compilateur sera toujours plus obtus que toi, et qu'il refusera systématiquement que tu crées une instance de la classe de base, on dit que la classe est abstraite, par opposition à concrète : qui représente quelque chose qui existe réellement: un véhicule est quelque chose d'abstrait (c'est le concept d'un objet qui permet de se déplacer), mais voiture, vélo, camion, avion, bateau, train, et j'en passe sont autant de types concrets (que tu peux croiser en te baladant).
Si tu croise "un véhicule", il s'agira, quoi qu'il advienne, d'un des types concrets sus-cités (ou de ceux que j'ai oublié de citer :D ) ;)
Au niveau de l'héritage, il faut savoir que toute classe héritant (de manière directe ou indirecte) d'une classe abstraite hérite de... toutes les fonctions virtuelles pures qui n'ont pas encore été redéfinies seront, elles aussi, des classes abstraites.
En effet, tu peux très bien faire hériter une de tes classe d'une classe qui hérite d'une classe abstraite et qui, parce qu'elle dispose de données permettant de fournir un comportement à quelques fonctions virtuelles pures, et qui le font, mais qui laissent, malgré tout, "un certain nombre" de fonctions virtuelles pures sans implémentation.
Les fonctions qui auront été redéfinies dans la classe "intermédiaire" peuvent, si leur comportement convient à ta classe "finale", ne pas être redéfinies, mais, s'il reste ne serait-ce qu'une seule fonction virtuelle pure déclarée par "l'un des ancêtres de ta classe" qui n'est pas redéfinie dans ta classe "finale", ta classe sera, forcément, une classe abstraite nécessitant d'être, elle aussi, héritée (en espérant que, cette fois, on puisse disposer des informations qui permettront de fournir l'implémentation à la fonction virtuelle pure qui n'a pas encore été redéfinie afin que nous disposions, enfin, d'une classe concrète qui pourra être instanciée) ;)