Tout simplement en C++11 :
Code : Sélectionner tout - Visualiser dans une fenêtre à part Mask(std::vector<int> const & a, std::vector<int> const & b) : Mask(Box(a, b)) { }
Tout simplement en C++11 :
Code : Sélectionner tout - Visualiser dans une fenêtre à part Mask(std::vector<int> const & a, std::vector<int> const & b) : Mask(Box(a, b)) { }
à condition toute fois d'avoir un compilateur assez récent.
Par contre, tu pourrais avoir une méthode static qui fais la même chose:
Code : Sélectionner tout - Visualiser dans une fenêtre à part Mask Mask::create(std::vector<int> const & a, const std::vector<int>& b) {return Mask(Box(a, b));}
Perdu
std::vector::swap
Autant pour moi, je corrige ça
La classe Box étant templatisée, il faut préciser le type associer lorsqu'on déclare un Box.
Ceci dit donc, voici ma classe réécrite:
En C++11 donc, on peut faire un appel à un autre constructeur déjà défini.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 class Mask { public: //Mask() {}; Mask(int sizex, int sizey, int originX=0, int originY=0):_sx(sizex),_sy(sizey),_ox(originX),_oy(originY){}; Mask(const Box<int>& box):_sx(box.sizeX()),_sy(box.sizeY()),_ox(box.GetA()[0]),_oy(box.GetA()[1]){}; //Mask(const std::vector<int>& A,const std::vector<int>& B):Mask(Box<int>(A,B)){}; // A partir de C++11 Mask(const std::vector<int>& A,const std::vector<int>& B){*this=createMask(A,B);}; Mask createMask(std::vector<int> const & a, const std::vector<int>& b) {return Mask(Box<int>(a, b));}; ~Mask(){}; protected: int _sx,_sy,_ox,_oy; };
Sinon, en réutilisant ta méthode static leternel, on pourrait imaginer écrire le constructeur comme ci-dessus.
L'écriture est correcte? Car j'ai jamais utilisé un "*this=qqch"... Je suppose que ça fait appel au constructeur de copie de la classe, qui ici n'étant pas spécifié est donc celui par défaut qui se contente de recopier les attributs. Corriger moi si je me trompe.
Mes premiers tests ont l'air de fonctionner avec cette façon de faire.
*this = va modifier l'objet précédemment construit, en l'écrabouillant.
Ca n'est pas ça que tu souhaites.
En fait, tu peux être plus extrême.
Pour l'exercice, mets _sx et _sy en constantes. Et nomme les _width et _height, c'est quand même plus clair.
(de même, ox et oy serait left et top).
Dans ces conditions, ton constructeur n'a pas d'autre choix que d'être explicite sur _width et _height.
Du coup, tu n'a pas d'autre choix que de ne pas proposer le constructeur à deux vecteur, mais de contraindre à l'usage de createMask.
Au passage, comme tu es dans Mask, la fonction peut se limiter à create().
Je pensais à une fonction libre.
Les alternatives sont nombreuses:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 //idiome des créateurs: namespace geom { class Mask { protected: Mask(int width, int height, int originX=0, int originY=0): width(width),height(height),left(originX),top(originY){} public: ~Mask(){}; static Mask create(int width, int height, int originX=0, int originY=0) {return Mask(width, height, originX, originY);} static Mask create(Box<int> const& box) {return Mask(box.sizeX(), box.sizeY(), box.GetA()[0], box.GetA()[1]);} static Mask create(std::vector<int> const & a, std::vector<int> const& b) {return create(Box<int>(a, b));} protected: int width,height; int left,top; }; }//geom:: using geom::Mask; int main() { Mask a = Mask::create(...); return 0; }
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 //idiome des constructeurs nommés: namespace geom { class Mask { protected: Mask(int width, int height, int originX=0, int originY=0): width(width),height(height),left(originX),top(originY){} public: ~Mask(){}; static Mask precise(int width, int height, int originX=0, int originY=0) {return Mask(width, height, originX, originY);} static Mask like(Box<int> const& box) {return Mask(box.sizeX(), box.sizeY(), box.GetA()[0], box.GetA()[1]);} static Mask around(std::vector<int> const & a, std::vector<int> const& b) {return create(Box<int>(a, b));} protected: int width,height; int left,top; }; }//geom:: using geom::Mask; int main() { Mask a = Mask::precise(12,10,5,5); return 0; }
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
32
33
34 //variante fonction libre des créateurs: namespace geom { class Mask { protected: Mask(int width, int height, int originX=0, int originY=0): width(width),height(height),left(originX),top(originY){} public: ~Mask(){}; protected: int width,height; int left,top; friend Mask create(int width, int height, int originX, int originY); friend Mask create(Box<int> const& box); friend Mask create(std::vector<int> const & a, std::vector<int> const& b; }; inline Mask createMask(int width, int height, int originX=0, int originY=0) { return Mask(width, height, originX, originY); } inline Mask createMask(Box<int> const& box) { return Mask(box.sizeX(), box.sizeY(), box.GetA()[0], box.GetA()[1]); } inline Mask createMask(std::vector<int> const & a, std::vector<int> const& b) { return create(Box<int>(a, b)); } }//geom:: int main() { geom::Mask a = geom::createMask(...); return 0; }Dans les quatre cas, j'ai supprimé le problème de la construction, spécifique, vu que tu as déjà un constructeur direct.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 //les factories, qui prolonge le précédent. namespace geom { class Mask { protected: Mask(int width, int height, int originX=0, int originY=0): width(width),height(height),left(originX),top(originY){} public: ~Mask(){}; protected: int width,height; int left,top; friend class MaskFactory; friend class RepetitiveMaskFactory; }; class MaskFactory { public: Mask create(int width, int height, int originX=0, int originY=0) { return Mask(width, height, originX, originY); } Mask create(Box<int> const& box) { return Mask(box.sizeX(), box.sizeY(), box.GetA()[0], box.GetA()[1]); } Mask create(std::vector<int> const & a, std::vector<int> const& b) { return create(Box<int>(a, b)); } }; class RepetitiveMaskFactory { public: const int width, height; public: RepetitiveMaskFactory(int width, int height) : width(width), (height) {} Mask createAt(int x=0, int y=0) { return Mask(width, height, x, y); } Mask createAt(Mask const& other) { return Mask(width, height, other.left, other.top); } }; }//geom:: using namespace geom; int main() { MaskFactory factory; Mask a = factory.create(50, 50, 8, 12); RepetitiveMaskFactory creator(12, 18); Mask b = creator.createAt(a); Mask c = creator.createAt(1, 2); return 0; }
PS aux candidats rouspéteurs: c'est à dessein que j'utilise le même nom pour l'argument du constructeur direct est pour le champ.
Le C++ le permet, et pour constructeur sans corps, c'est simple que jouer avec les préfixes/suffixes.
Merci bien pour se tour d'horizon des possibilités.
Si j'analyse, dans tous les cas, le constructeur de base de la classe est mit en protected.
On contraint alors l'utilisateur à déclarer un objet Mask non pas via une déclaration du type
mais grâce à des méthodes de création d'objet:
Code : Sélectionner tout - Visualiser dans une fenêtre à part Mask mon_mask(paramètre...)
Pour ce qui est de la méthode des factories (que je ne connaissais pas et qui portent bien leur nom)
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 Mask mon_mask=Mask::create(paramètre...) // ou Mask mon_mask2=CreateMask(paramètre...)
on créer donc une classe dont le but est d'instancier un objet Mask. Une usine à Mask littéralement.
Une certaine lourdeur dont je peux me passer ici je pense (autant faire le plus simple possible), mais je garde en tête cet exemple qui m'auras initier aux factories et qui me resserviras un jour j'en suis sûr.
Pour les besoins que j'en ai je vais prendre ta solution proposée en premier (idiome des créateurs).
Merci à vous tous pour vos réactions.
Les factories sont un des design pattern les plus classiques, parce qu'ils permettent de combiner deux niveaux d'informations: un paramétrage général et les cas précis.
Il suffit que le paramétrage général soit conservé dans des champs internes.
C'est ce que j'ai représenté avec RepetitiveMaskFactory.
Salut,Voilà une très mauvaise idée...
De manière générale, tu ne devrais avoir que des comportements (AKA fonctions), éventuellement susceptibles d'être redéfinis dans les classes dérivées (AKA fonctions virtuelles) si cela fait sens dans l'accessibilité publique et dans l'accessibilité protégée, et si tu veux vraiment faire les choses de manière, tu essayeras de limiter les accesseurs (AKA "getter") au maximum et tu refuseras avec obstination toute solution à base de mutateurs (AKA setters).
En effet, si tu décides de placer une variable membre dans une accessibilité autre que l'accessibilité privée, tu donnes implicitement le droit à "tout le monde" (même si tu mets une légère restriction au fait que tout le monde se réduit aux classes dérivées en ce qui concerne l'accessibilité protégée) d'aller modifier à sa guise la donnée en question. Cela conduit immanquablement à une perte totale du contrôle que tu peux avoir sur le nombre de fonctions qui iront la modifier, et rend toute tentative d'encapsulation et de programmation par contrat bancale.
Si tu as du mal à comprendre ce que j'explique ou que tu n'arrives pas à comprendre les raisons qui m'incitent à te donner ce conseil, n'hésite pas à me le faire savoir, je développerai volontiers chaque point plus ou moins obscure
Je suis tout à fait d'accord avec toi sur ce point.
Pour moi, une variable est constante ou privée.
Et à priori, même constante, jamais publique.
Certain parleront des static const, mais je n'en vois jamais de vraie utilité.
En général, ce sont des globales déguisées, et je préfère proposer des fonctions statiques qui s'en servent.
Et, lg_53, quand koala01 dit développera, crois-le, car il s'en fera un plaisir... et toi un long moment de lecture
Le dévoleppement d'un code bien encapsulé étant une très longue histoire (je vous crois) et n'étant pas le sujet de départ, je vais passé le topic en résolu pour ne pas perdre un lecteur qui passerait par là.
Mon habitude de mettre protected au lieu de private provient certainement d'une expérience que j'ai eu: une fois, j'ai eu besoin d'améliorer une classe développée par quelqu'un d'autre, sans pouvoir la modifier. Sauf que dans cette classe tous les attributs était privés et la classe ne comportait aucun Setter.
Résultat: j'ai dû dupliquer un bout de code beaucoup trop gros pour être normalement dupliqué, jusqu'à ce que l'auteur de cette classe ait changé le private en protected ce qui m'a permis de pouvoir écrire une classe fille. Voici donc pour la petite histoire.
Merci encore à tous.
lg_53
Je comprend parfaitement que tu aies pu avoir une (voir plusieurs) expérience(s) malheureuse(s), mais elle(s) ne devrai(en)t pas te servir de prétexte à la prise de (très) mauvaises habitudes!!!
Il faut savoir que la notion d'encapsulation est régie par une loi appelée loi de Déméter. Cette loi nous dit que, si une class A manipule en interne un objet de type B, l'utilisateur de la classe A ne devrait pas avoir à connaitre le type B pour pouvoir utiliser la classe A.
A partir de là, il faut comprendre que ce ne sont pas les données qui ont de l'importance lorsque l'on décide de créer un agrégat de données (car on peut dire exactement la même chose pour les classes que pour les structures) : ce sont les services qu'il peut nous rendre et les ordres auxquelles il est susceptible de réagir.
On peut à la limite classer les différentes fonctions qui nous permettent de manipuler une classe en deux grandes catégories : les fonctions qui nous permettent d'interroger l'objet afin d'en connaitre l'état à un point de vue donné et celles qui nous permettent d'agir afin de modifier un état particulier.
Les accesseurs entrent bien évidemment dans la première, les mutateurs (setters) entrent quant à eux dans la seconde. Mais, pour ce qui concerne les accesseurs, il n'y a du sens à en proposer un que si l'on peut considérer que le fait d'être en mesure d'accéder à la donnée en question fait partie des services que l'on est en droit d'attendre de la part de la classe.
Par exemple, si tu as une classe Coordonnée qui représente une coordonnée sur un référentiel à deux dimensions, nous sommes en effet en droit de nous attendre à être en mesure de récupérer l'abscisse et l'ordonnée correspondant à une coordonnée particulière.
Mais si tu as une classe Voiture qui dispose (forcément) d'un membre de type ReservoirACarburant, il n'y a absolument aucun sens à fournir un accesseur sur le réservoir en question : on s'attend uniquement à ce que la voiture nous propose un certains nombre de services qui seront sans doute en relation avec la présence du réservoir, mais on ne s'attend absolument pas à être en mesure d'accéder directement au réservoir.
Nous aurons une jauge à carburant nous indiquant la proportion existante entre la quantité actuelle de carburant et la contenance maximale du carburant, nous disposerons peut être de l'affichage d'une évaluation du ombre de kilomètres que l'on peut encore parcourir et nous aurons bien sur une trappe par laquelle nous pourrons rajouter du carburant qui finira forcément dans le réservoir, mais, à moins d'être mécanicien -- et encore -- on n'a absolument aucun besoin de pouvoir accéder directement au réservoir... si tant est qu'il y en a réellement un (comprend: qu'il existe bel et bien une classe Reservoir et que les données relatives au carburant soient bel et bien maintenues par un membre de ce type)
A partir de là, on peut déjà se dire que, si l'on ne juge pas utile de fournir un accesseur sur un membre donné, il n'y a absolument aucune raison à fournir un mutateur sur cette donnée. Quant à celle pour lesquelles on a décidé de fournir un accesseur, le mutateur est sans doute la plus mauvaise des solutions que nous pourrions envisager.
En effet, l'utilisation d'un mutateur implique que l'on crée un nouvel objet du type (correspondant au type du membre à modifier) dont on a besoin avant de le mettre dans un état "que l'on croit pouvoir être correct" et de fournir cet objet à l'objet que l'on veut modifier.
Le gros problème de cette logique, c'est que tout le contrôle échoit à l'utilisateur : c'est à celui qui va utiliser le setter de s'assurer que l'élément transmis sera dans un état cohérent, alors qu'il n'a peut être qu'un idée trop vague de toutes les conditions qui permettront de dire sans l'ombre d'un doute que "cet état est compatible avec celui auquel on est en droit de s'attendre de la part de notre objet".
Prenons l'exemple d'une d'une classe qui peut être positionnée dans un référentiel à deux dimensions pour te permettre de comprendre. Cette caractéristique est sans doute représentée au niveau des membres de la classe par un membre de type Coordonnée dont je parlais là tantôt. Si cette classe propose une fonction setPosition, c'est à l'utilisateur de s'assurer que la position est correcte, que l'instance de la classe peut effectivement se retrouver à la position indiquée.
Mettons que la position en question corresponde au milieu d'un océan. Les seuls objets réellement susceptibles d'accéder à cette position sont les objets de type "navires" (ou sous marins) et de type "avion" (ou similaires), mais une voiture ne pourra en aucun cas accéder à cette position. Si l'utilisateur doit prendre en compte tous les types d'éléments positionnables et tous les caractéristiques des différents endroits afin de déterminer si un objet bien particulier peut effectivement accéder à une position donnée, je peux te garantir qu'il oubliera systématiquement une ou l'autre combinaison.
Plutôt que de fournir un mutateur sur la position, nous avons donc intérêt à fournir un service "move(diffX, diffY)" ou "moveTo(posX, posY)" qui permettra à l'objet positionnable de s'assurer qu'il peut effectivement atteindre la position à laquelle on lui demande de se rendre. C'est notre objet qui devient responsable de la manière dont son état est modifié.
La différence ne tient pas uniquement au terme utilisé : il ne s'agit pas uniquement d'utiliser moveTo au lieu de setPosition pour que la magie opère. il faut que moveTo mette tout ce qui est nécessaire en oeuvre pour s'assurer que l'objet déplacé puisse effectivement atteindre la position à laquelle on souhaite qu'il se rendre. Mais, comme la vérification sera différente pour un avion, pour un sous-marin et pour un véhicule, si c'est l'objet lui-même qui effectue la vérification, nous pouvons espérer que le développeur de la classe aura pris en compte toutes les conditions qui doivent être remplies ou qui peuvent empêcher un objet de type particulier d'atteindre une position d'un type donné. Et l'utilisateur de la classe n'a plus qu'à s'inquiéter d'interroger l'objet pour savoir s'il est en mesure d'atteindre la position indiquée
Si tu as compris ce que j'essayais de te dire, tu comprendra assez facilement le reste :
Les variables membres doivent être placées dans une accessibilités privée pour qu'il n'y ai jamais que les fonctions membres de la classe qui puissent y accéder (que ce soit pour les interroger ou pour les modifier). Les accessibilités publiques et protégées ne devraient contenir que des fonctions membres, qui seront placées dans l'accessibilité publique s'il s'agit de services que "monsieur tout le monde" doit pouvoir utiliser, dans l'accessibilité protégée si ce sont des services auxquels seules les classes dérivées doivent avoir accès.
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager