Oui, bientôt sans doute :P
Version imprimable
Pour ma part, je vois surtout les propriétés comme une manière propre et élégante d'avoir des attributs vus comme des variables sans devoir les déclarer publics et/ou risquer l'incohérence en cas "d'oubli" d'appel d'un accesseur... Cela permet également d'avoir des attributs réellement write/read only, et cela centralise à l'endroit où l'on définit la propriété sa valeur par défaut (au lieu d'en avoir un bout dans le constructeur), bref c'est nettement plus propre et lisible. Tout comme le With du Pascal continue de me manquer sévèrement en C/C++ lors de la manipulation de structures / classes un peu complexes, même après plus de 10 ans de C/C++ intensif...
Après, une "vraie" propriété est normalement très contrainte au niveau des prototypes, on ne peut donc pas mettre n'importe quoi dessus. Mais c'est extrêmement agréable de pouvoir utiliser les "attributs" directement, comme des variables, en se contrefichant de savoir si oui ou non il y a une fonction à appeler derrière. En cas d'évolution (plus ou moins de contrôles requis par exemple, verrouillage d'un sens d'accès, changement d'implémentation), la syntaxe dans le programme appelant reste la même et, surtout, elle reste bien plus lisible qu'avec trois tonnes de fonctions s'appellant les unes les autres...
Entre un Object1.Attribute1 += Object2.Attribute2 ; et un Object1.SetAttribute1(Object1.GetAttribute1()+Object2.GetAttribute2()) ;, perso, je sais quelle écriture est la plus lisible... ;)
Pour ma part, quand j'ai la possibilité d'utiliser des propriétés, je déclare directement les propriétés correspondant aux attributs... Quitte à les laisser en read-only.
Certes, je suis "marqué" par les propriétés sous Delphi, mais elles ont été implémentées également sous BCB, de la même façon, et ça n'a pas trop l'air de poser problème jusqu'à présent.
Super, c'est exactement pour toutes ces raison que j'aime bien les propriétés.
Le fait est que, en étant à peine un peu exhaustif, tu ne devrais pas voir à écrire un code proche de
ni un code proche deCode:Object1.Attribute1 += Object2.Attribute2 ;
En effet, ces codes devraient, en toute bonne logique, être intégrés dans un comportement que l'on souhaite autoriser (et qui doit donc être pris en compte au moment de la conception) pour la classe en question.Code:Object1.SetAttribute1(Object1.GetAttribute1()+Object2.GetAttribute2()) ;
Cela pourrait, pour le premier, prendre la forme de
et, pour le second etre proche deCode:
1
2
3
4 void MaClass::Add(Maclass const & rhs) { Attribute1+=rhs.Attribute1; }
car les approches que je présente permettent de *réellement* encapsuler Attribute1 et de respecter demeter.Code:
1
2
3
4 void MaClass::synchronize(MaClass const & rhs) { Attribute1=rhs.Attribute1; }
J'ai, encore une fois, l'impression que vous avez des approches, si pas fragmentaires, en tout cas restrictives ou erronées de ce que peuvent représenter l'encapsulation, la loi demeter, ou le concept orienté objet lui-même.
J'admets que, dans certains cas, un accesseur puisse s'avérer intéressant, mais, très souvent, il subira l'énorme inconvénient de fournir un acces "global" (dans le sens de disponible dans n'importe quelle portion de code) à quelque chose dont l'acces devrait être particulièrement restreint.
Et, comme de bien entendu, j'émets ces critiques de manière encore plus virulente vis à vis... des mutateurs...
Je ne suis pas un ferme partisan de la loi de Demeter, je dois dire, pour des raisons d'efficacité principalement. Dans mon domaine (embarqué temps réel), c'est un élément crucial et non-négociable.
Toutefois, je ne vois vraiment pas en quoi une propriété viole cette règle, dans le sens qu'elle ne permet ni plus, ni moins de choses qu'un accesseur... Après, si effectivement tu publies par propriété/accesseur un attribut d'une sous-classe, tu t'exposes à un paquet d'emm.... gros comme ça, mais là encore, le concept de propriété n'est en rien responsable de ça.
Au contraire : pour respecter Demeter, tu vas souvent devoir écrire un (trop) grand nombre de wrappers, chose lourde et franchement pesante. Les propriétés permettent de faire ceci de façon nettement plus élégante qu'une tripaille d'accesseurs/mutateurs/foncteurs, et n'interdisent absolument pas la création de méthodes de "service macroscopique" avec propagation dudit service aux sous-classes...
Par rapport aux exemple précités : c'est pourtant un cas tout à fait légitime d'additionner deux attributs différents de deux objets différents... D'une part, deux objets peuvent parfaitement décrire un système, mais de deux points de vue différents, et ne pas se connaître ni de près, ni de loin. D'autre part, l'attribut de l'un peut refléter l'attribut de l'autre. De plus, tu es parti du principe que cela représentait deux instance de la même classe : et pourquoi donc devrait-ce être le cas ? ;)
Reformulé, on pourrait écrire : StatisticObject.TotalNumberOfCommands += aTempCommandObject.Size ; : cela a un sens, et permet d'éviter de créer une fonction spécifique d'ajout (imposant de connaître les types de classes impliqués) dans une fonction qui n'a de sens que pour le programme appelant, ou de créer une classe spécifique pour centraliser ces deux sous-classes... Tu ne peux pas créer des prototypes pour toutes les références croisées possibles et imaginables, c'est une évidence. Tu ne peux pas non plus prévoir toutes les utilisations possibles et imaginables de ta classe si elle est un tant soit peu générique / bas niveau. Tu vas donc reporter ce problème vers l'appelant en l'obligeant à définir un paquet de classes d'adaptation.
Tu vas me dire "Quel intérêt de faire ça ?", peut-être : ben c'est d'un intérêt évident quand tu écris beaucoup de code-passerelle, c'est à dire du code de transfert entre deux mondes. Or, beaucoup de mes librairies sont justement utilisées par un code de ce type. Actuellement, en C++, je n'ai pas d'autre choix que de fournir des accesseurs, ou directement l'attribut dans certains cas... Performances et simplicité d'utilisation obligent.
Le déport dans le code appelant au travers des propriétés (ou des accesseurs) est, justement, une partie de la solution à ce problème. Certes, dans du code 100% métier, ce cas de figure apparaît rarement. Quand tu fais des librairies génériques dans 99% des cas, comme moi, tu apprends vite à savoir déléguer à l'appelant et/ou à imposer des contrats. De plus, je dois avouer que la "pureté" du code source et le respect de Grands Principes (les majuscules sont volontaires) sont le cadet de mes soucis : mon but premier, à la création d'une classe, est d'avoir une API claire, concise et pratique à utiliser. Le reste, c'est ma tripaille interne et ma sauce, je n'ai pas à exposer à l'utilisateur mes contraintes de code et/ou mes évolutions d'interface.
Or, justement, les propriétés me permettent de pas mal blinder à ce niveau sans rendre le code appelant infect... J'ai tendance à pleurer à chaque fois que je vois un code généré C++ de lecture XML, où l'on finit par penser que le concepteur du générateur a été payé à la parenthèse produite ! ;) Les propriétés simplifieraient grandement ce type de code, sans modifier en quoi que ce soit les fonctionnalités ou les restrictions d'accès.
Question de point de vue, donc...
Une propriété fait ce que tu veux qu'elle fasse. Tu peux l'utiliser pour n'importe quelle donnée, et définir le niveau d'accès que tu souhaites avoir (R, W, RW).
J'ai également beaucoup de mal à voir en quoi restreindre l'accès à une donnée comme la cardinalité d'un container, ou l'accès aux éléments unitaires, pose le moindre problème.
Typiquement, une propriété peut remplacer les opérateurs () et [] de façon tout à fait confortable, en n'autorisant que la lecture et non pas l'écriture.
Ce que je vois surtout, c'est que le programme appelant n'est pas à modifier lorsque l'on transforme un attribut "simple" (= une variable de classe uniquement, donc) en un attribut "complexe" (= sa modification déclenche un trigger interne). Typiquement, tu peux transformer une classe relativement figée et statique en quelque chose de dynamique réagissant "en direct-live" à ses modifications.
Après, je te concède que le niveau de visibilité published peut avoir son importance dans une implémentation correcte des propriétés, tout comme des concepts tels que le inherited ou le deprecated...
N.B. : les liens de ce paragraphe renvoient vers des cours/posts Delphi.
C'est quand même un cas concret dans le développement en situation réelle : on implémente une première version d'un composant, qui va ensuite évoluer en quelque chose de plus complexe. Au moins, le code source appelant n'a pas à être modifié, et cela a évité de créer initialement des tonnes d'accesseurs plus ou moins inutiles (et coûteux en temps de développement / tests !!).
D'un point de vue strictement pratique, j'ai rarement vu quelqu'un dire que la VCL Borland soit mal foutue côté architecture... Elle fait pourtant un usage plus qu'intensif et constant des propriétés, et c'est aussi pour ça qu'elle est simple et pratique d'utilisation... :mrgreen:
la VCL est mal foutue (il fallait bien quelqu'un pour le dire...)
les objets melangent plusieurs cmportements (beaucoup, en fait) parce qu'apparemment quand on fait de l'IHM on ne connait rien a la composition, on ne connait que l'héritage (c'est un trait très commun qui se retrouve partout de toutes facons)
=> classes bloatées qui ont 43 rôles différents.
un exemple simple : si je veux une image qui se comporte comme un bouton, comment je fais ?
dériver de bouton et redéfinir la fonction d'image?
dériver d'image et redéfinir la fonction de clic ?
dériver des deux a la fois ?
si le comportement (ce qui se passe lorsque je clique...) n'avait pas été integré au rendu (comment déssiner le composant) alors ca serait très simple; il y aurait un comportement "bouton" et un widget "image", il suffit de coller ces deux la ensemble et on a une image cliquable.
MacLak tu parles de la lisibilité, je trouve que
est particulièrement crade si l'un des deux a un effet de bord quelconque, et très illisible. si ce ne sont que des attributs publics (ce qui ne change rien a l'affaire)on est sur qu'il n'y a pas d'effet de bord. Si cette ligne se transforme en appel de fonctions, moi je râle; je ne veux pas aller voir ce que la propriété machin fait réellement.Code:StatisticObject.TotalNumberOfCommands += aTempCommandObject.Size ;
lorsqu'il y a une propriété il peut y avoir mensonge de l'interface; par exemple, une propriété "size" qui calcule la taille en O(n) comme pour une liste chaînée, si c'est une méthode j'irai faire attention. si c'est un attribut, c'est simplement stocké.
et si c'est une propriété, ca ressemble a un attribut mais en fait ca mobilise le processeur.
Mais si c'est un attribut, tu veux aussi interdire la modification directe depuis l'extérieur de ta classe Liste, donc on est d'accord que ça passe obligatoirement par une méthode dans les deux cas.
Si tu fais un size() ou un count(), tu n'as pas réellement d'indication non plus sur ce qui est fait sous le rideau. Cela pourrait être un simple "return this->size".
En fait ce qui te dérange c'est plutôt la confusion possible entre une variable membre et la propriété du point de vue de la syntaxe d'écriture? Je pense que c'est une question d'habitude et de documentation.
Pas forcément...
C'est là que peut entrer en jeu l'amitié qui, si elle est correctement utilisée, renforcera encore l'encapsulation ;)
Rien ne t'empêche, en t'aidant de l'amitié, de créer une classe dont la responsabilité sera d'assurer la conversion de ces deux types qui ne "se connaissent absolument pas" ;)Citation:
Par rapport aux exemple précités : c'est pourtant un cas tout à fait légitime d'additionner deux attributs différents de deux objets différents... D'une part, deux objets peuvent parfaitement décrire un système, mais de deux points de vue différents, et ne pas se connaître ni de près, ni de loin. D'autre part, l'attribut de l'un peut refléter l'attribut de l'autre. De plus, tu es parti du principe que cela représentait deux instance de la même classe : et pourquoi donc devrait-ce être le cas ? ;)
Reformulé, on pourrait écrire : StatisticObject.TotalNumberOfCommands += aTempCommandObject.Size ; : cela a un sens, et permet d'éviter de créer une fonction spécifique d'ajout (imposant de connaître les types de classes impliqués) dans une fonction qui n'a de sens que pour le programme appelant, ou de créer une classe spécifique pour centraliser ces deux sous-classes...
Avec les template, tu peux en arriver à n'écrire qu'une fois les choses ;)Citation:
Tu ne peux pas créer des prototypes pour toutes les références croisées possibles et imaginables, c'est une évidence. Tu ne peux pas non plus prévoir toutes les utilisations possibles et imaginables de ta classe si elle est un tant soit peu générique / bas niveau. Tu vas donc reporter ce problème vers l'appelant en l'obligeant à définir un paquet de classes d'adaptation.
tu devrais peut être relire cette entrée de la FAQ en ce qui concerne l'amité ;)Citation:
Tu vas me dire "Quel intérêt de faire ça ?", peut-être : ben c'est d'un intérêt évident quand tu écris beaucoup de code-passerelle, c'est à dire du code de transfert entre deux mondes. Or, beaucoup de mes librairies sont justement utilisées par un code de ce type. Actuellement, en C++, je n'ai pas d'autre choix que de fournir des accesseurs, ou directement l'attribut dans certains cas... Performances et simplicité d'utilisation obligent.
Les deux ne sont absolument pas antinomiques, sans passer par les propriétés ;)Citation:
Quand tu fais des librairies génériques dans 99% des cas, comme moi, tu apprends vite à savoir déléguer à l'appelant et/ou à imposer des contrats. De plus, je dois avouer que la "pureté" du code source et le respect de Grands Principes (les majuscules sont volontaires) sont le cadet de mes soucis : mon but premier, à la création d'une classe, est d'avoir une API claire, concise et pratique à utiliser. Le reste, c'est ma tripaille interne et ma sauce, je n'ai pas à exposer à l'utilisateur mes contraintes de code et/ou mes évolutions d'interface.
J'ai bien compris qu'un propriété peut être, au choix, un accesseur, un mutateur ou le couple des eux...Citation:
Une propriété fait ce que tu veux qu'elle fasse. Tu peux l'utiliser pour n'importe quelle donnée, et définir le niveau d'accès que tu souhaites avoir (R, W, RW).
Il n'empêche que cela renvoi un "détail d'implémentation" qui n'a, normalement, souvent pas lieu d'être exposé...
La cardinalité d'un containeur, si la classe agit comme tel, cela ne me dérange pas si on l'expose, mais je ne vois pas en quoi écrire monContainer.size est plus facile que d'écrire monContainer.size()...Citation:
J'ai également beaucoup de mal à voir en quoi restreindre l'accès à une donnée comme la cardinalité d'un container, ou l'accès aux éléments unitaires, pose le moindre problème.
D'autant plus que nous avons, comme le fait remarquer screetch, qu'au moins, nous sommes en mesure, dans le deuxième cas, de nous rendre plus facilement compte qu'il s'agit, effectivement, d'une fonction
[/QUOTE]Le problème de lecture et / ou de l'écriture n'en est pas un, étant donné que nous convenons tous les deux que rien n'empêche de prévoir les restrictions utiles, à la manière de ce que le sucre syntaxique remplace.Citation:
Typiquement, une propriété peut remplacer les opérateurs () et [] de façon tout à fait confortable, en n'autorisant que la lecture et non pas l'écriture.
Le problème est, surtout pour l'opérateur [], que tu expose un détail d'implémentation qu'il serait bien plus intéressant d'exposer sous la forme d'un itérateur (parce que, si tu dois renvoyer l'objet contenu dans une liste, l'opérateur [] n'a strictement plus lieur d'être...)
Ce que je vois surtout, c'est que le programme appelant n'est pas à modifier lorsque l'on transforme un attribut "simple" (= une variable de classe uniquement, donc) en un attribut "complexe" (= sa modification déclenche un trigger interne). Typiquement, tu peux transformer une classe relativement figée et statique en quelque chose de dynamique réagissant "en direct-live" à ses modifications.
On en revient toujours au même problème, qui est que les mutateurs et ou les accesseurs sont, par nature, à éviter...Citation:
C'est quand même un cas concret dans le développement en situation réelle : on implémente une première version d'un composant, qui va ensuite évoluer en quelque chose de plus complexe. Au moins, le code source appelant n'a pas à être modifié, et cela a évité de créer initialement des tonnes d'accesseurs plus ou moins inutiles (et coûteux en temps de développement / tests !!).
Une fois que tu as compris cela ( c'est ce que je répète depuis de nombreux posts), on comprend pourquoi les propriétés sont, elles aussi, à éviter.
Si, pour une raison ou une autre, une modification doit être apportée, il est bien plus intéressant d'invoquer une fonction dont le nom correspond au comportement attendu et de lui fournir les arguments nécessaires, sans que cette fonction ne s'appelle setMachin, d'autant plus que l'on n'a normalement pas à s'inquiéter, en invoquant cette fonction, de la valeur de l'attribut avant la modification: C'est à la fonction de vérifier s'il faut apporter ou non la modification demandée.
Evidemment, lorsqu'il s'agit d'un container, le fait de savoir de combien d'objet il est composé fait partie des services que l'on attend de sa part...
Mais je ne vois pas en quoi cela simplifie les chose d'avoir une propriété size au lieu de la fonction membre constante size.
Tellement mal foutue que Lazarus cherche absolument à la reproduire à 100%, au lieu d'utiliser quelque chose d'autre... :mrgreen:
Tu vas avoir du mal de dériver des deux à la fois... Delphi ne supporte pas l'héritage multiple.
Quant au faux problème du comportement, c'est très simple pourtant : tu définis un bouton (comportement le plus complexe, donc celui que l'on ne "refait" pas), et tu écris sa méthode Paint... Éventuellement en appelant le Paint d'une image non stockée sur la fiche, d'ailleurs. Tu as très exactement le même problème avec les MFC ou l'API Win32, et c'est d'ailleurs tout à fait normal : tout est canvas et window, les variantes de comportement ne sont justement que des variantes.
Autre solution, nettement plus élégante : tu prends un TImageButton et tu règles ses propriétés correctement... :mrgreen:
VCL = Visual Component Library, pour mémoire et information. C'est une bibliothèque RAD, et non pas une base élémentaire comme pourraient l'être les MFC (où le "F" signifie Foundation, je te rappelle, et non pas F**king Very High Level Interface).
En VCL, tu rends une image cliquable très simplement, la différence étant que, justement, tu n'as PAS besoin de séparer le comportement du rendu dans 99.99% des cas... Les composants sont donc adaptés au cas nominal, interdire ou augmenter ce comportement nominal prendra du temps. Réciproquement, en MFC, c'est plutôt "tout ce qui n'est pas codé est inexistant" : ça fait des exécutables plus compacts, certes, mais dans 90% des cas tu passes ton temps à redéfinir 20 fois les mêmes choses.
Mais vouloir un comportement "roots" avec des outils RAD (ou le contraire, d'ailleurs), tu n'as pas l'impression de t'être planté de librairie ? ;)
Arrêtes un peu la mauvaise foi : c'est exactement pareil quand tu appelles une méthode qui te renvoie soit un attribut directement, soit qui effectue un comportement complexe. Tu n'as PAS à savoir ce que ça fait, et tu as des contrats si l'état initial / final ont une importance quelconque (= des effets de bord). Tu as aussi une documentation de la classe, et avec des avertissements sur les coûts CPU anormaux si elle n'a pas été écrite par un incapable fini.
Les problèmes que tu décris sont plus liés à une documentation minable et une conception faite pendant un abrutissement devant la Star'Ac qu'à l'utilisation de propriétés à la place d'accesseurs (ou vice-versa, d'ailleurs).
C'est exactement la même chose pour les méthodes : ce que tu décris, c'est simplement du code de goret, peu importe la manière dont il est appelé. Obtenir la taille d'une liste chaînée en la parcourant à chaque fois, faut ne pas être fini et/ou n'avoir absolument aucune conscience des performances...
Une bonne propriété size renvoie un attribut interne à la lecture, redimensionne le container à l'écriture, et l'attribut est mis à jour à chaque modification du container. Bref, un size() + resize() en un seul point d'accès logique, au lieu de devoir retenir deux fonctions, et le tout prévu pour un minimum syndical de performances.
le RAD est hors sujet ici
les propriétés sont un détail, avoir des parenthèses ou pas ca n'a aucune incidence
permet d'ecrireCode:
1
2
3
4
5
6
7 class AvecAccesseur { int m_value; public: int& value() { return m_value; } const int& value() const { return m_value; } };
pas de get, pas de set. c'est vraiment un détail que ca soit possible de retirer les parenthèses en D et pas en C++, franchement.Code:avecAccesseur.value() += avecAutreAccesseur.value();
ce qui est interessant avec les propriétés c'est surtout la sérialisation et 'introspection, le reste est plutôt anecdotique. c'est comme dire que "struct" est un meilleur mot-lé que "class" ou l'inverse, ca n'apporte rien au débat.
Je te conseille de regarder un peu WPF. C'est usine çà gaz par moment, mais ils ont une manière de gérer ce genre de situation assez différente, qui me semble bien plus intéressante. En gros, un bouton peut contenir d'autres contrôles, par exemple, une image, u texte, une listbox... D'ailleurs, un élément d'une listbox peut elle aussi contenir d'autres contrôles...
L'amitié impose de pouvoir modifier la classe ciblée, ce qui n'est pas toujours ni possible, ni souhaitable.
Ce qui impose l'écriture d'une classe de plus... Ce que justement, je cherche à éviter comme la peste. Une classe wrapper, c'est des lignes de code en plus, donc une source de bug potentielle. Autant limiter ça au code appelant : si tu veux généraliser le principe (et rendre ta classe réutilisable), ta classe-wrapper devra ne pas introduire de comportements spécifiques au code métier appelant, donc ne réellement définir QUE du wrapping... Ce qui fait que tu vas, au final, écrire du code proche (sur le fond) de celui des propriétés bien que différent dans la forme.
Si, par contre, elle introduit du code métier, alors elle n'est plus générique, devient spécifique au code métier appelant, et n'a alors plus aucune raison d'être car elle introduit une couche supplémentaire inutile car non réutilisable.
Encore faut-il que ce soit possible, et c'est rarement le cas. Pour te donner un exemple, tu vas avoir du mal à définir un comportement commun (et donc une interface "unique") entre un bus CAN et des lignes analogiques... Pourtant, il peut être utile de connaître le nombre total de données acquises/transmises, peu importe le médium. Un template va t'imposer, à certains moments, l'écriture d'une interface "commune" qui pourrait très bien ne pas avoir réellement de sens pour l'une des classes, et n'en avoir que pour l'appelant. Tu vas donc complexifier une classe avec des informations "inutiles" pour respecter une interface commune : OK, mais franchement, je trouve que c'est du boulot inutile et pas forcément réutilisable (donc pas nécessaire de se taper une interface from scratch pour ça).
Pas plus qu'un accesseur et/ou un attribut public, y compris "public" via un friend.
Le fait que size() n'est pas affectable, contrairement à la propriété homonyme, donc tu devras écrire une méthode resize() supplémentaire.
Pour augmenter ta capacité de 10 éléments, tu devras donc écrire monContainer.resize(monContainer.size()+10) au lieu de monContainer.size+=10... Ou développer une troisième méthode "relative_resize()" qui prends un delta.
Cela commence à faire beaucoup de méthodes et des lignes d'appel bien longues, je trouve !! ;)
Et j'y réponds la même chose : utilises des classes mieux documentées et/ou ne les fais pas faire par des incapables.
En quoi est-ce un détail d'implémentation ??? Que tu appelles l'obtention d'une cardinalité "property size", "getSize()", "count()" ou "size()", c'est la même chose derrière. Simplement, certaines formes d'écriture sont plus pratiques pour l'appelant. Toutes ces formes exposent autant (ou aussi peu, je dirais) les détails d'implémentation réels derrière.
Ce qui va rendre l'écriture de containers un minimum performants assez difficile... :twisted:
Que tu appelles ta méthode de lecture getElementAt(i) ou [ i ], quelle est la différence réelle ? Pour moi, les deux sont des accesseurs... Le premier est simplement plus lourd et pénible à utiliser que le second.
De plus, comme les deux formes sont (sémantiquement parlant) équivalentes, il n'y a pas plus de problèmes à définir un itérateur avec une propriété qu'avec des méthodes.
Ce qui revient pour moi à la même chose : tu n'as finalement fait que renommer ton accesseur/mutateur mais il remplit exactement le même rôle. Un accesseur n'est pas caractérisé par un "get" ou "set" en préfixe, j'espère que l'on est d'accord sur ce point...
Cf. exemple d'utilisation ci-dessus, avec le resize.
Si c'est sans importance, pourquoi tu combats autant le fait de les enlever ? Faudrait savoir, ou être cohérent... :mrgreen:
Désolé, mais là, c'était trop tentant.
Parce que tu as pris l'habitude de te taper ça. Moi, j'ai pris l'habitude d'avoir plus simple et, surtout, d'avoir quelque chose de nettement plus proche du raisonnement humain tout en restant à un niveau d'efficacité proche de l'optimal. Via des propriétés, la taille (pour continuer cet exemple) est alors une entité à part entière, UNIQUE, que tu manipules en fonction de tes besoins et non pas quelque chose de "diffus" accédé au travers de 3 ou 4 fonctions plus ou moins cohérentes.
Via des méthodes classiques, la seule façon d'arriver à ce niveau de centralisation serait de définir une sous-classe "Size", avec quelques primitives, qui permettrait d'avoir un point central d'accès aux fonctions liées à la taille du container.
Ce qui alourdit très nettement l'écriture et l'utilisation dudit container, sans parler des risques de pertes de performances... Inconvénients que n'ont pas les propriétés. La "tripaille" interne reste tout aussi "diffuse" avec les propriétés (plusieurs méthodes privées nécessaires pour réaliser les actions atomiques), donc pas de changements majeurs pour le développeur, mais l'interface publique (celle de l'utilisateur) est naturellement centralisée.
Parce que tu appelles ça documenter ???? Pour toi, documenter, c'est simplement donner un nom à peu près correct à une méthode, et point barre ???
Ben ils sont vachement tolérants, dans ta boîte... Si un de mes dévs me fait ce coup-là sur une doc client, je le crucifie.
Ce qui pour moi est rédhibitoire pour faire des IHMs. C'est certes bien mieux que les MFC, mais c'est loin d'être parfait ou du niveau d'un vrai RAD. Et il se pose le problème (plus ou moins épineux) de la connexion avec des DLL natives : autant certains cas de figure sont très simples, autant d'autres sont franchement galères vu l'impossibilité de franchir (via debug) la barrière entre le natif et le managé.
Si ce n'esxt que l'amitié permet de n'exposer quelque chose qu'à ce qui en a réellement besoin.
Par nature, toute fonction publique (qu'il s'agisse d'un accesseur, d'un mutateur, d'une fonction normale) est susceptible d'être appelée de "n'importe où dans le code", dés le moment où le contenu de l'objet est connu.
Or, je suis désolé, mais il y a quantité de chose qui doivent n'être connues que d'un minimum d'autres choses extérieures.
De ce point de vue, l'amitié est, définitivement, la meilleure manière de travailler, si l'on excepte l'héritage (qui n'est pas toujours cohérent)
Le principe des propriétés, encore une fois, c'est que cela facilite le fait d'exposer des choses qui... auraient en tout casdu être exposées de manière beaucoup plus restrictive.
Au contraire, si une même fonction a -- en dehors du polymorphisme, qui garde quand même la sémantique de la fonction -- deux comportement clairement opposés, c'est plutôt le signe d'une mauvaise compréhension de ce que l'on attend de cette fonction..Citation:
Le fait que size() n'est pas affectable, contrairement à la propriété homonyme, donc tu devras écrire une méthode resize() supplémentaire.
Pour augmenter ta capacité de 10 éléments, tu devras donc écrireTu sembles oublier que, lorsque tu crées un conteneur, il ne suffit pas de modifier la valeur d'un attribut pour, effectivement, redimensionner ce container...Citation:
monContainer.resize(monContainer.size()+10) au lieu de monContainer.size+=10... Ou développer une troisième méthode "relative_resize()" qui prends un delta.
De plus, pour redimensionner un container, tu as classiquement trois souhaits possibles:Dans le premier cas, il s'agit de redimensionner le conteneur sur base de la taille d'un autre conteneur (pas forcément compatible), d'un compteur ou d'une information récupérée par ailleurs.
- le redimensionner à une valeur déterminée, indépendante de la taille d'origine
- Réduire sa taille de un (ou de plusieurs) élément(s)
- augmenter sa taille de un (ou de plusieurs) élément(s)
Dans le deuxième cas, on peut estimer que cette diminution de la taille correspond... au retrait d'élément du conteneur
Et, dans le troisième cas, on peut, à l'inverse, estimer que cette augmentation de taille correspond... à l'ajout d'élément dans le contenur.
Nous avons donc bel et bien affaire à... trois comportements différents qui...méritent d'être clairement différenciés.
Maintenant, si la fonction d'ajout ou de retrait d'élément utilise resize, ma fois, pourquoi pas :question:
Après tout, un simple
le fait très bien, quand ce n'est pas, encore mieux unCode:
1
2
3
4
5
6 void MaClass::add(/*...*/) { resize(m_size+1); /* suite de la logique, dépedant du conteneur mis en oeuvre }
Code:
1
2
3
4 void MaClass::add(/*...*/) { m_items.add(/*...*/) }
Beaucoup de méthodes... faut pas exagérer...Citation:
Cela commence à faire beaucoup de méthodes et des lignes d'appel bien longues, je trouve !! ;)
Dans le cadre de la gestion de la taille, tu as, au total quatre fonctions à créer, qui, au moins, définissent clairement ce qu'elles font:
- size() : renvoi du nombre d'élément
- resize(int) : redéfini le nombre d'éléments en fonction du nombre donné en paramètre
- add( type) : ajoute un objet
- remove(iterateur) : retire un objet
Bon, allez, je t'accorde que, pour add et remove, on peut aussi envisager une version prenant un itérateur de début et un itérateur de fin
et, en terme de ligne de code, ca ne fait pas grand chose non plus.
Par contre, cela permet d'avoir des comportement clairement définis, qui ne risquent pas de prêter à confusion
Et j'y réponds la même chose : utilises des classes mieux documentées et/ou ne les fais pas faire par des incapables.
En quoi est-ce un détail d'implémentation ??? Que tu appelles l'obtention d'une cardinalité "property size", "getSize()", "count()" ou "size()", c'est la même chose derrière. Simplement, certaines formes d'écriture sont plus pratiques pour l'appelant. Toutes ces formes exposent autant (ou aussi peu, je dirais) les détails d'implémentation réels derrière.
Ce qui va rendre l'écriture de containers un minimum performants assez difficile... :twisted:
La sémantique que l'on accorde généralement à l'opérateur ()...Citation:
Que tu appelles ta méthode de lecture getElementAt(i) ou [ i ], quelle est la différence réelle ?
Cela n'a l'air de rien, mais, modifier la sémantique d'un opérateur ou d'une fonction est la pire des choses qui soient.
Ainsi, l'opérateur [] véhicule une sémantique d'acces direct, aléatoire.
C'est très bien lorsque la collection, effectivement, basée sur un tableau, mais cela perd tout son sens lorsque la collection est basée sur... une liste ou n'importe quoi d'autre...
C'est la raison pour laquelle, plutôt que de renvoyer une référence (constante ou non)sur l'élément de la collection, je suis clairement partisan de... définir un itérateur imbriqué et d'utiliser de préférence celui-ci comme valeur de retour, si tant est, toujours, qu'il soit opportun de permettre "à l'extérieur" d'accéder au contenu de la collection.
De cette manière, tu arrives même à éviter, au minimum dans une certaine mesure, la réécriture des fonctions qui utilisent l'itérateur si, un jour, tu décide de modifier la manière dont la collection est effectivement gérée en interne.
Comme je l'ai dit plus haut, tu fait l'impasse sur la sémantique, et c'est bien dommage ;)Citation:
Pour moi, les deux sont des accesseurs... Le premier est simplement plus lourd et pénible à utiliser que le second.
Le fait est que la sémantique que tu vas donner au nom de ta fonction importe tout autant que le comportement qu'elle met en oeuvre.Citation:
Ce qui revient pour moi à la même chose : tu n'as finalement fait que renommer ton accesseur/mutateur mais il remplit exactement le même rôle. Un accesseur n'est pas caractérisé par un "get" ou "set" en préfixe, j'espère que l'on est d'accord sur ce point...
Nous sommes, effectivement, d'accord sur le fait qu'un accesseur n'est pas caractérisé par la présence du préfixe "get" ou "set", mais, là où nous ne sommes visiblement pas d'accord c'est:
- sur le fait qu'une fonction doit avoir un comportement déterminé, et non un comportement susceptible de varier selon les circonstances
- sur le fait que le meilleur moyen pour quelqu'un qui lit le code le comprenne facilement, c'est que le nom de chaque fonction représente clairement le comportement qu'elle met en oeuvre
- sur le fait qu'il faut autant que faire se peut éviter de changer la sémantique généralement accordée à des fonctions ou des opérateurs couremment utilisés par ailleurs
Et, au passage, de manière générale, un accesseur non constant est, pour moi, une aberration... Surtout s'il renvoie une référence sur l'attribut (cf effective C++, entre autres)
Ma priorité n'est pas que je puisse avoir le meilleur design mais que ce design puisse changer, et si possible rapidement. Je ne me pose pas la question si je dois faire un accesseur, un mutateur, etc... tant que c'est documenté.
Les propriétés, quand elles sont bien faites, permettent de rajouter comme un espèce de wrapper autour des fonctionnalités offertes par une classe. Si il y a une modif à faire, hop, on change un champ en méthode mais le contrat et le code de l'appelant reste le même.
Ce qui était vraiment bien en Delphi, c'est que ce wrapper ne coûtait rien en performance vu qu'on pouvait mapper une propriété sur un champ. Donc on pouvait les utiliser tout le temps sans que ca fasse plein d'appels de méthode en plus.
En fait je pense qu'il faut avoir codé sous Delphi pour comprendre cet intérêt (que MacLak a bien expliqué) et combien ca accélère les choses.
Renvoyer une reférence sur un objet en C++, ce n'est pas pareil, ca ne permet pas de transformer un champ en méthode de manière transparente.
Je ne vois pas pourquoi enlever ces parenthèses est si grave que cela. C'est au contraire un moyen d'abstraction intéressant je trouves.
Pour le reste, les variadic templates et le fait de pouvoir faire des templates sur du int, float ou même char[], c'est quand même le bonheur. Ça n'a pas été cités parmis les top features manquantes au C++. Pourtant, c'est parmis celles dont je me sers le plus.
certes, si on pouvait passer a autre chose que les property maintenant...
Ou de complexifier inutilement quelque chose qui ne doit pas l'être... Chaque médaille a son revers, tu le sais aussi bien que moi.
Et alors ? Si c'est en lecture seule, ça n'a aucune importance, sauf celui de te permettre d'avoir du code d'introspection.
Si c'est en écriture, tu as des tonnes de mécanismes permettant de blinder cet aspect (sections critiques, enregistrement via par exemple le TLS, encapsulation et/ou private implementation, singletons, pointeurs avec compteur de référence, et j'en passe).
On est bien d'accord. Je te rappelle qu'une propriété est forcément publique, donc remplace purement et simplement la publication des accesseurs (qui deviennent/restent privés), OU permet une centralisation d'un concept sur un seul point d'accès (typiquement, la gestion de la taille / capacité d'un container, justement).
Rien ne t'oblige à publier 100% de tes attributs dans des propriétés RW, ni à remplacer le concept d'attribut par celui de propriété.
Une propriété est une interface de la classe : si quelque chose ne doit pas être manipulé de l'extérieur, pourquoi diable voudrais-tu en faire une propriété ???
Pas plus qu'un accesseur... C'est très exactement la même chose, seul l'utilisateur voit une différence réelle entre les deux.
Avec une propriété, si... ;) Du moins, c'est l'impression qu'aura l'utilisateur.
Tu me reparles de multiples fonctions de redimensionnement, moi je te parle d'un concept unique de taille (ou capacité, si tu préfères). Cela n'empêche absolument pas d'utiliser une méthode Add() pour ajouter un élément, wrappant au besoin un sous-container. Quand aux fonctions de modification de taille, il est souhaitable qu'elles existent de façon unitaire (erase(), resize(), incsize(), decsize(), que sais-je encore...) et qu'elles soient utilisées au sein de l'interface "Write" de la propriété, en fonction de la valeur passée en paramètre.
Bref, c'est à toi de t'assurer que tu respectes ton contrat, c'est à dire que ta taille a été augmentée d'une unité après un ajout... Le formalisme utilisé (méthodes ou propriété) n'a absolument aucune importance, sauf pour l'utilisateur final qui y gagne en simplicité.
Je trouve plus confusant d'avoir N points d'accès à un concept donné (ex : la capacité d'un container) au lieu d'un seul et unique, dont la valeur ou le cadre d'utilisation (L/R-Value) est proche de la représentation "humaine" dudit concept. Une taille/capacité se lit, se détermine, peut augmenter, diminuer, être réduite à néant. C'est une formalisation assez proche de ce que l'on trouve fréquemment dans les spécifications de besoin, en fait.
[mode bash]
Ah, la force de l'inertie... ;)
[/mode bash]
Blague à part : taper dans une map de la STL se fait également via [], simplement tu n'as pas toute la finesse de contrôle qui est possible via find/insert. Et alors ? Les exceptions existent pour ça, ce sont effectivement des comportements non-nominaux de tenter de récupérer un élément inexistant et/ou d'écraser un existant, mais ça peut parfaitement convenir à la plupart des besoins.
Pourquoi s'emm.... avec un itérateur et des méthodes find/insert, alors que l'on contrôle en amont ce qui est lu/écrit dans la map, et que les exceptions (cas anormal et rarissime) conviennent parfaitement à la gestion des erreurs ? L'opérateur [] fait parfaitement l'affaire, est cent fois plus simple à utiliser, et répond au besoin. Utiliser autre chose, c'est simplement aimer se compliquer la vie pour rien.
Pour toi, peut-être. Pour moi, c'est un index, ni plus, ni moins, avec des limites hautes et basses et (par défaut) pas de "trous" dans son domaine. Son caractère aléatoire et/ou direct m'importe peu.
Je le vois comme "méthode pratique et basique d'accès", ni plus, ni moins. Je ne pars pas du principe que c'est l'accès le plus performant (même si c'est souvent le cas), je pars du principe que c'est le plus PRATIQUE. C'est très différent.
Notamment pour les maps, surtout quand je les utilise façon tableau associatif des langages interprétés : c'est nettement plus pratique pour la configuration lue de façon "rare" dans le programme, la configuration lue "intensivement" étant de toutes façons convertie dans un format plus efficace.
A titre personnel, j'ai récemment écrit une classe qui possède deux opérateurs [] : le premier prends un entier en paramètre, est est effectivement LA méthode la plus efficace pour accéder à un élément donné (accès direct, aléatoire et par référence).
L'opérateur est surchargé pour accepter, en paramètre, une chaîne de caractères. Dans ce cas précis, la recherche est séquentielle (O(N)), et sert à retrouver un élément via un critère de haut niveau, demandé de façon rare mais qui évite à l'utilisateur de se rappeler des numéros plus ou moins abstraits pour accéder aux données.
Et moi, ça m'évite de faire une map / classe d'association dédiée à ça alors que c'est une fonction accessoire, pour ne pas dire une scorie...
Heu... On ne doit pas avoir le même sens pour sémantique (="le fond"), que j'associe en général au mot "syntaxe" (="la forme").
La sémantique, c'est l'opération à effectuer (récupérer un élément, avoir la taille du container, etc.). La syntaxe, c'est la forme que cela prends (propriété, méthode, accesseur, service réseau, glutz vénusien...).
Sûr... Va dire ça à la méthode connect() d'une socket, suivant si le câble réseau est branché ou pas, ou si le DHCP a filé une passerelle ou pas, ou si ça répond ou pas, etc.
Encore heureux que les fonctions s'adaptent aux circonstances : comment voudrais-tu faire de la gestion d'erreurs sans ça ???
Sans même parler des surcharges de méthodes...
Je pense que tu as voulu dire autre chose que ce que je suis en train de lire : peux-tu reformuler, stp ?
Ce qui est le cas d'une propriété, clairement définie comme telle bien entendu, qui regroupe via un seul identifiant un concept complet telle que la taille / capacité d'un container, au lieu de disperser cette information sur N entités diverses.
Donc, un seul point à documenter / assimiler, et tu as l'accès à toutes les fonctions liées à un concept. Difficile de faire plus simple, plus concis et plus compréhensible, je trouve.
Il faut surtout éviter des présuppositions qui sont, à mon sens, dangereuses... Comme croire qu'un accès par [] est forcément direct et aléatoire, alors que la STL te prouve le contraire sur ses propres containers. Ou encore croire qu'un container nommé "Liste" est forcément lent, séquentiel et tutti-quanti, qu'une map est forcément une association complexe (une LUT n'est qu'un cas particulier de map), etc.
Tu as aussi d'autre présuppositions malheureuses, comme de présupposer du rôle d'une fonction size() : c'est quoi, le couple (size,count), ou le couple (capacity,size) ?? En fonction des frameworks / langages / habitudes, c'est l'un, ou l'autre... Pour moi, c'est en général le premier cas par exemple, je récupère habituellement la cardinalité via count plutôt que size, si je n'ai pas de contraintes spécifiques.
Des concepts comme les propriétés permettent de rendre les classes plus "intelligentes", sans pour autant complexifier leur interface publique, ni les rendre spécialement moins performantes, ni spécialement plus longues à écrire. Pour ma part, je trouve que ça va dans le bon sens, étant donné que développeur comme utilisateur y gagnent.
Bon, désolé du pavé, mais t'es au moins "aussi pire" que moi au niveau longueur des posts... ;)
il ne s'agit pas de documenter un bidule, il s'agit que le bidule soit auto-documenté, grâce a un nom non pas "a peu près" correct mais _exact_
resize() fait exactement ce que son nom indique.
size non, et a besoin d'une documentation.
operator[] n'est pas clair non plus. la preuve ? tu t'es trompé dans sa description.
insert() est très clair lui.
Mouais. Peut-être.
Je sens quand même un poil de mauvaise foi, ou peut être une indication de dissonance cognitive (va savoir ; je pencherais presque pour le second, tant il me parait plus honnête que le premier).
En quoi un accès à conséquences multiple à une unique entité est plus explicite qu'un accès à conséquence unique à de multiples entités ? En quoi est-ce différent du raisonnement humain de dire que resize() change la taille telle que donnée par size() ?
A mon avis, mais je peux me tromper, tu confonds simplicité et simplisme.
On se fait un petit plaisir, ne parlons pas de performance. Il n'y a de toute façon pas de différence majeure en termes de performance entre l'appel explicite d'un mutateur et l'appel implicite de celui ci via l'utilisation d'une propriété. C'est dans les deux cas le compilateur qui fait le sale boulot d'optimisation, quitte à aller jusqu'à l'inlining complet de l'appel.
Je ne suis pas vraiment d'accord avec toi en ce qui concerne les qualités des propriétés au niveau de l'interface publique d'une classe. La "centralisation" (je pense que je vois ce que tu veux dire) n'est pas un but en soi. Parmi les buts qu'on se doit d'essayer d'atteindre lors de la définition d'une interface, citons la cohérence, la lisibilité, et l'expressivité. D'autres buts sont bien évidemment à atteindre. La centralisation telle que tu la présente a des effets plus ou moins néfaste sur certains de ces buts.
- lisibilité : une propriété représente quelque chose d'intrinsèque à l'objet. Hors le principe même de la programmation orientée objet, c'est de cacher les données intrinsèque et de déterminer les traitements (qui vont être décrit sous forme de méthodes) que peut subir cet objet - dans le cadre de la résolution d'un problème défini. Il est beaucoup plus aisé pour nous de réfléchir en termes d'actions qu'en terme de définition (sinon les maths seraient facile pour tout le monde ; la dernière fois que j'ai vérifié, ça n'était pas le cas). Un programme est une suite de traitement - ce n'est pas une collection de donnée. Mélanger définition et traitement dans une même section de code rends le code plus difficile à déchiffrer.
- expressivité : une fonction fait une chose unique (dans l'idéal). Une propriété peut être accédée de manière différente (par exemple en lecture et en écriture). Elle fait donc plus de chose, mais est décrite avec un nom unique. L'utilisation d'un nom unique réduit d'autant l'expressivité de ce nom - par rapport à l'utilisation de plusieurs noms différents, chacun lié à une action particulière.
On peut argumenter sur le fait que les propriétés ne servent à rien. Dans l'ideal, il devrait être possible d'écrire un programme sans accesseurs et sans mutateurs, et donc sans propriétés. Un conteneur n'a pas besoin d'avoir une propriété Size si je peux utiliser les algorithmes du conteneur (par exemple une méthode foreach(), ...).
Ne te méprends pas : cet argument est volontairement idéaliste (et prends comme point de départ une application vertueuse de la loi de Demeter) - il n'a pas pour but de passer le test de la pratique (en tout cas, pas de ta pratique ; je pense que je peux y arriver, en tout cas pour certains problèmes simples, tout en respectant MON style de programmation). En fait, dans le projet que je réalise actuellement pour le compte d'un client, les seuls accesseurs présents sont utilisé dans les tests unitaires (pour vérifier que (par exemple) les séquences d'initialisation se passent correctement). Un seul est utilisé dans le code réel de l'application - dans le contexte de la récupération de valeurs lue dans un fichier de configuration au format XML. Le projet n'est pas vraiment de taille mirobolante, mais c'est quand même un projet pro, avec les contraintes liées à ce type de projet.
Je regrette mais non, pas du tout. Les propriétés permettent justement d'être expressif là où on ne peut pas avec des appels de fonctions.
Si le même nom existe pour la lecture et l'écriture ET que la séquence d'action est cohérente, alors on est plus expressif (transmettre l'idée).
Pour un exemple imaginaire où vous mourrez d'envie d'agrandir de 4 la place disponible dans un tableau:
En D:
et même depuis quelques temps :Code:
1
2
3
4 A[] tableau; ... tableau.size = tableau.size + 4;
En C++:Code:
1
2
3
4 A[] tableau; ... tableau.size += 4;
Quelle version est plus lisible et plus expressive ? Vous trouvez peut-être le C++ plus lisible car vous êtes familier avec, mais c'est tout.Code:
1
2
3
4 std::vector<A> tableau; ... tableau.resize (tableau.size() + 4);
Qui plus est en C++ je vais évidemment lire la doc vu que resize appelle un constructeur par défaut sur chaque nouvel élément alloué, et que ca a des implications...
Si je n'avais rien à faire de l'expressivité, j'écrirais du COBOL.
c'est plus une question de choix, une opinion, qu'un fait. Tu peux debattre de cela 20 ans, je ne changerai pas d'idée et je vois mal mac lak changer d'idée non plus.
Je ne faisais que répondre à des trucs comme:
Effectivement on changera pas d'avis.Citation:
resize() fait exactement ce que son nom indique.
size non, et a besoin d'une documentation.
Bon après l'expressivité c'est rarement définitif, on peut surcharger des opérateurs et s'amuser avec le compilo un peu pour rendre le code plus "à son goût". L'aspect intéressant c'est l'aspect "philosophique" -- quoique, je n'utiliserais peut-être pas le mot "intéressant" pour la philosophie sur la supériorité/infériorité des propriétés par rapport aux bons vieux getters/setters.
C'est réciproque, on peut jouer longtemps comme ça...
Même pas : le "re" sous-entends que l'opération est une modification à chaud, et non pas une primo-allocation. Pour faire plus clair, c'est comme si tu me disais que tu utilises realloc à la place de malloc de façon constante. Le nom serait exact si c'était "setsize", et non pas "resize"... Et une documentation est nécessaire pour savoir si les éléments sont conservés ou pas : on va donc l'appeler "setsizewithelementconservationexceptifsizeisreduced"... Ouais, super.
Mmm ?? Dis-moi donc où, stp...
C'est plutôt que j'ai l'habitude de les utiliser, et que respecter mordicus Demeter produit, dans ma branche, des bouses infectes surmultipliant les couches, les interfaces et plombant les perfs : difficile donc d'y adhérer.
Côté perso, et dév de haut niveau, c'est plutôt des fonctions de très haut niveau, et les propriétés permettent une manière simple et pratique d'utiliser les classes : là encore, Demeter serait surtout une nuisance côté application finale, en alourdissant le code inutilement.
Si c'est la même chose, en quoi le définir en accès unique est-il un problème ? ;)
Avoir l'équivalent, en terme d'unicité d'accès, sans utiliser de propriétés revient à déclarer une sous-classe très fortement imbriquée dans sa classe parent, contrôlant justement tous les aspects liés à la taille.
C'est plus simple dans le sens où tu n'as qu'un seul accès pour un concept donné, donc inutile de chercher à côté s'il existe autre chose : tout est là.
Je ne vois pas en quoi écrire "myClass.Size = 20 ;" est difficile à comprendre d'un point de vue syntaxique, ou même sémantique... La taille du machin est mise à 20, point, c'est simple et net.
Non, c'est surtout que je suis assez souvent amené à créer des librairies, les utiliser moi-même dans l'évolution commandée, et filer ensuite le bébé à quelqu'un d'autre pour la maintenance.
Via des interfaces simples, la plupart du temps, personne ne vient me poser de questions idiotes. Via des usines à gaz complexes privilégiant la sacro-sainte encapsulation à outrance, on vient me chercher à chaque fois que quelqu'un touche le code... Dans les deux cas, pourtant, je te promets que la doc est aussi correcte et complète que possible.
C'est en partie vrai seulement, mais admettons, pour des cas relativement simples tout du moins.
Pour le développeur ? Bien entendu... La philo Unix est même basée sur un principe similaire, d'ailleurs. Mais pour l'utilisateur, par contre, non : c'est au contraire quelque chose de crucial.
Si tu veux une analogie, c'est la différence entre une aide façon Windows (avec index, recherche, etc.) et une aide façon manpage. Dans le premier cas, tu peux chercher en fonction du but désiré. Dans le second, il vaut mieux connaître le nom de la fonction pour en avoir l'aide.
Pour ma part, j'ai les deux casquettes à la fois : je développe des API pour les autres, et j'en utilise moi-même (y compris les miennes, bien sûr). Je sais aussi que ça vaut le coup de passer 5% de temps de plus sur le dév, pour faire de "l'inutile" fonctionnel, mais qui me permettra d'avoir une paix royale par la suite sur l'utilisation.
Côté code interne à la classe ?? Mais quel besoin as-tu d'y mettre les mains pour l'utiliser, de façon générale ?? Quant aux données intrinsèques... C'est impossible d'avoir, de façon constante et systématique, l'absence totale de publication d'éléments intrinsèques. La taille d'un container devra forcément être publiée d'une façon ou d'une autre, un itérateur est une manière également de parcourir la structure interne d'une classe, etc. Si l'on raisonne sur le FOND et non pas sur la FORME, l'exposition de données intrinsèques est obligatoire.
Pour le reste, j'ai plus de facilité à suivre les données que les actions, au contraire. Heureusement, la plupart des informaticiens sont à peu près bons en maths.
Pour les non-informaticiens, j'ai souvent moins de mal à leur représenter les concepts inclus dans une classe sous forme d'entité pouvant être manipulée que sous forme d'actions élémentaires incluses dans la classe parent.
Heu... Ne me dis pas que tu ne distingues pas une L-Value d'une R-Value, quand même. Ce n'est pas la même chose de lire et écrire une entité, même si ce sont bien entendu des opérations complémentaires. Je ne vois pas en quoi son rôle n'est pas unique... Tout comme une fonctionnalité peut être effectuée de plusieurs manières différentes sur les containers STL, par exemple : tu peux faire un erase() ou un resize(0), c'est pareil : où est l'unicité de la fonction, dans ce cas ??
Ou le contraire : ça t'évite d'avoir plusieurs noms redondants pour décrire un concept unique. A condition d'arrêter de voir une propriété comme un attribut bête et méchant, et de la prendre en tant que concept, bien sûr.
Une propriété n'est PAS un attribut, même si elle s'utilise de la même manière. Ce serait aussi idiot que de dire qu'une fonction C et une fonction macro (préprocesseur) sont la même chose, juste parce qu'elles ont des parenthèses et des paramètres.
Certes. Derrière, par contre, tu oublies que l'on a aussi des contraintes budgétaires, calendaires,techniques et humaines. OK, c'est idéaliste, mais c'est justement pour ça que ce n'est pas applicable. En pratique, ce n'est d'ailleurs pas de l'idéalisme, mais de l'utopie.
Justement, tu as dis le mot : "simples"... Réussir à transformer une problématique complexe en un programme performant, on sait tous que c'est tout sauf trivial (et ceci que l'on soit collé au matériel ou dans les domaines de très haut niveau, d'ailleurs).
Le plus souvent, on arrive à un résultat performant, certes, mais difficile d'utilisation. Quel est le besoin qui pousse la plupart des dévs à se contenter de ceci, au lieu de simplifier les interfaces pour l'utilisateur et donc de "simplifier" le problème général pour celui qui utilise la boîte noire ??
Tout dépend de ce que tu appelles "taille mirobolante". Pour ma part, on est une vingtaine en permanence sur le projet, qui dure depuis presque 10 ans, et avec un turn-over énorme. Crois-moi, dans ce genre de cas, tu cherches au maximum à simplifier les interfaces d'utilisation, quitte à rendre le code interne (mais normalement caché) un peu plus difficile d'accès. De toutes façons, il est nettement plus souvent utilisé que modifié, donc ça convient très bien de privilégier l'utilisation à la modification.
D'un point de vue "conception interne de la classe et méthodes à développer", les propriétés ne simplifient RIEN. Par contre, elles simplifient l'utilisation de la classe, en centralisant les points d'accès et en simplifiant les appels. Ce n'est que de la syntaxe, la sémantique est très exactement la même que les accesseurs, mutateurs ou attributs suivant ce que l'on décide de mettre en propriété. Cela ne t'empêche pas de faire ce que tu veux (elles n'ont rien d'obligatoire), et permet de méchamment simplifier des interfaces qui pourraient devenir franchement infectes. Bref, où est le problème ??
Là, j'ai l'impression de voir un remake du bon vieux troll "caster le malloc en C" : on hurle sur quelque chose qui ne gêne que les puristes, simplifie la vie des utilisateurs (et je rappelle que l'on code pour que ce soit utilisé, hein, pas pour mettre en vitrine), et pour lesquels les "désavantages" sont des problèmes largement plus issus de la conception que de l'implémentation...
de plus je ne vois pas le rapport avec findCitation:
Blague à part : taper dans une map de la STL se fait également via [], simplement tu n'as pas toute la finesse de contrôle qui est possible via find/insert. Et alors ? Les exceptions existent pour ça, ce sont effectivement des comportements non-nominaux de tenter de récupérer un élément inexistant et/ou d'écraser un existant, mais ça peut parfaitement convenir à la plupart des besoins.
C'est justement là le problème...
Je suis bien d'accord qu'un acces en lecture ne pose-- de manière très théorique du moins, à condition de faire l'impasse sur les accès concurrent -- à peu près jamais de problème.
Par contre, un acces en écriture se doit d'être aussi encadré que possible et d'être permis de manière aussi restreinte que possible
Comme les propriétés, même si elles permettent le choix de l'acces qu'elles autorisent, sont, par défaut, d'accès "global" (comprend: accesssible depuis n'importe quel endroit du code, même s'il y a des conditions à remplir), tu permet à tout le code d'avoir un acces en écriture sur des objets au sujet desquels un tel acces devrait être particulièrement restreint.
Je suis, encore une fois, désolé d'insister sur ce point, mais c'est loin d'amener une situation idéale ;)
Le problème, c'est, encore une fois, que, à faciliter la mise en oeuvre de chose dont l'usage devrait à chaque fois être décidé après mure réflexion, tu n'incite les gens à créer des propriétés "par habitude", y compris pour des attributs qui n'ont nul besoin d'être représenté sous cette forme, et, pire encore, tu encourage le développeur (vu que c'est l'une des possibilités de la propriété) à fournir un acces en écriture à un attribut qui, non seulement, ne devrait déjà pas être accessible en lecture "depuis l'extérieur", mais au sujet duquel il est encore plus incohérent d'envisager d'en permettre un acces en écriture.Citation:
Rien ne t'oblige à publier 100% de tes attributs dans des propriétés RW, ni à remplacer le concept d'attribut par celui de propriété.
Une propriété est une interface de la classe : si quelque chose ne doit pas être manipulé de l'extérieur, pourquoi diable voudrais-tu en faire une propriété ???
Bien sur, tu me dira qu'il faut laisser les "bons" programmeurs faire le boulot, mais je te rappelle encore une fois que tous ne sont pas aussi bons qu'ils ne peuvent le laisser croire...
Ca facilite grandement, au contraire, vu, qu'à l'extrême limite, il suffit d'une ligne de code pour... exposer aussi bien le mutateur que l'accesseur...Citation:
Pas plus qu'un accesseur... C'est très exactement la même chose, seul l'utilisateur voit une différence réelle entre les deux.
Et c'est presque plus un risque qu'un avantage...Citation:
Avec une propriété, si... ;) Du moins, c'est l'impression qu'aura l'utilisateur.
Tu le sais aussi bien que moi, l'homme est par nature un imbécile distrait.
Le programmeur n'étant qu'un homme, il est, lui aussi, un imbécile distrait (et je me mets volontiers en tête de liste ;))
Et tu connais surement tout aussi bien la loi de murphy, qui dit que, s'il est possible de faire une connerie, tu trouvera toujours quelqu'un pour la faire.
Au final, si tu donnes la possibilité à quelqu'un de modifier un attribut alors qu'il ne le devrais pas, tu dois t'attendre à rencontrer "un imbécile" pour essayer de le faire.
Bref, avec les propriétés, tu as beaucoup trop tendance à centraliser des comportements qui feraient beaucoup mieux... de rester clairement séparés.
Oui, si tu y tiens, rajoutons incsize et decsize... bien que je ne les ferais pas apparaitre dans l'interface publique (ne serait-ce que pour des problèmes d'initialisation de l'élément ajouté par incsize :P)Citation:
Tu me reparles de multiples fonctions de redimensionnement, moi je te parle d'un concept unique de taille (ou capacité, si tu préfères). Cela n'empêche absolument pas d'utiliser une méthode Add() pour ajouter un élément, wrappant au besoin un sous-container. Quand aux fonctions de modification de taille, il est souhaitable qu'elles existent de façon unitaire (erase(), resize(), incsize(), decsize(), que sais-je encore...) et qu'elles soient utilisées au sein de l'interface "Write" de la propriété, en fonction de la valeur passée en paramètre.
En simplicité et en capacité de faire des conneries, parfois même sans s'en rendre compte, parce qu'un caractère "malencontreux" peut te faire passer en "ecriture"...Citation:
Bref, c'est à toi de t'assurer que tu respectes ton contrat, c'est à dire que ta taille a été augmentée d'une unité après un ajout... Le formalisme utilisé (méthodes ou propriété) n'a absolument aucune importance, sauf pour l'utilisateur final qui y gagne en simplicité.
Un bête exemple, si l'auteur du code oublie le deuxième = lorsqu'il utilise la propriété "size" à l'intérieur d'un test, il aura modifié la taille de la collection, sans s'en apercevoir, et avec des conséquences pouvant être catastrophiques
Il n'y a rien de confus, et il n'y a même pas plusieurs point d'acces à un concept unique:Citation:
Je trouve plus confusant d'avoir N points d'accès à un concept donné (ex : la capacité d'un container) au lieu d'un seul et unique, dont la valeur ou le cadre d'utilisation (L/R-Value) est proche de la représentation "humaine" dudit concept. Une taille/capacité se lit, se détermine, peut augmenter, diminuer, être réduite à néant. C'est une formalisation assez proche de ce que l'on trouve fréquemment dans les spécifications de besoin, en fait.
Tu as, sans doute, un attribut qui garde en mémoire le nombre d'éléments que ta collection comporte, et tu as:
- une (série de) fonction(s) dont le comportement est clairement déterminé susceptible(s) de modifier cet attribut
- un seule et unique fonction te permettant de récupérer le nombre d'éléments contenu
A vrai dire, je fais partie de ceux qui regrettent profondément la présence de [] dans les std::map et qui regrettent encore plus profondément le fait que la sémantique qui est donné à cet opérateur permette aussi bien de rechercher un élément que d'en insérer un...Citation:
Blague à part : taper dans une map de la STL se fait également via [], simplement tu n'as pas toute la finesse de contrôle qui est possible via find/insert. Et alors ? Les exceptions existent pour ça, ce sont effectivement des comportements non-nominaux de tenter de récupérer un élément inexistant et/ou d'écraser un existant, mais ça peut parfaitement convenir à la plupart des besoins.
Pourquoi s'emm.... avec un itérateur et des méthodes find/insert, alors que l'on contrôle en amont ce qui est lu/écrit dans la map, et que les exceptions (cas anormal et rarissime) conviennent parfaitement à la gestion des erreurs ? L'opérateur [] fait parfaitement l'affaire, est cent fois plus simple à utiliser, et répond au besoin. Utiliser autre chose, c'est simplement aimer se compliquer la vie pour rien.
Peut être n'est ce que pour moi, mais, même si ce n'est que du à la force de l'habitude du C, j'estime que, bien que le fait qu'il permette d'avoir des limites hautes et basses, sa principale qualité tient, justement, dans le type d'acces aux différents éléments...Citation:
Pour toi, peut-être. Pour moi, c'est un index, ni plus, ni moins, avec des limites hautes et basses et (par défaut) pas de "trous" dans son domaine. Son caractère aléatoire et/ou direct m'importe peu.
Partique, peut être, mais, encore une fois, à quel prix :question:Citation:
Je le vois comme "méthode pratique et basique d'accès", ni plus, ni moins. Je ne pars pas du principe que c'est l'accès le plus performant (même si c'est souvent le cas), je pars du principe que c'est le plus PRATIQUE. C'est très différent.
La sémantique, c'est le sens que l'on donne (de manière conventionnelle ou consensuelle) à un terme.Citation:
Heu... On ne doit pas avoir le même sens pour sémantique (="le fond"), que j'associe en général au mot "syntaxe" (="la forme").
La sémantique, c'est l'opération à effectuer (récupérer un élément, avoir la taille du container, etc.). La syntaxe, c'est la forme que cela prends (propriété, méthode, accesseur, service réseau, glutz vénusien...).
C'est ce qui te permet de ne pas recevoir un couteau ou une fourchette lorsque tu demande une cuiller.
Si un terme a un double (ou pire, un triple/quadruple/... sens), il ne faut pas t'étonner si, à un moment donné, les gens se trompent sur le sens qu'il fallait donné à ce terme, c'est aussi simple que cela...
Encore une fois, en centralisant plusieurs comportements fondamentalement différents en un seul terme, tu augmente le risque de voir apparaitre des contre sens ou des erreurs dues, essentiellement... à la mauvaise évaluation du sens à donner à ce terme dans une situation donnée
Elles s'adaptent au circonstances, mais gardent des comportement dont le but estfondamentalement identique...Citation:
Encore heureux que les fonctions s'adaptent aux circonstances : comment voudrais-tu faire de la gestion d'erreurs sans ça ???
Sans même parler des surcharges de méthodes...
Je pense que tu as voulu dire autre chose que ce que je suis en train de lire : peux-tu reformuler, stp ?
Il est bien évident que, dés que tu as un test ou une boucle, ta fonction s'adapte aux circonstances, mais le but recherché par ta fonction est... fondamentalement unique
L'adaptation aux circonstances que tu permet au travers de propriétés (RW, je l'accorde), c'est d'avoir des comportements fondamentalement différents, et c'est là que repose le problème
Au niveau de la documentation à fournir, tu ne gagne pas grand chose à utiliser les propriétés, ne serait-ce que du seul fait que, si elles présentent deux comportement, tu devra quand meme documenter les deux comportements mis en oeuvre, et tu devra, en plus, indiquer dans quel cas tel ou tel comportement sera mis en oeuvre.Citation:
Ce qui est le cas d'une propriété, clairement définie comme telle bien entendu, qui regroupe via un seul identifiant un concept complet telle que la taille / capacité d'un container, au lieu de disperser cette information sur N entités diverses.
Donc, un seul point à documenter / assimiler, et tu as l'accès à toutes les fonctions liées à un concept. Difficile de faire plus simple, plus concis et plus compréhensible, je trouve.
Du point de vue de l'utilisation, je préfère avoir trois fonctions clairement distinctes pour lesquelles une simple phrase commençant par "cette fonction fait..." que d'avoir un "concept" (quelque part fort élargi :P) et devoir me rappeler de... tous les cas d'utilisation (si je fais ceci, j'aurai tel résultat, mais si je fais cela, j'aurai tel autre résultat, et, si d'aventure, j'ai une troisième possibilité, j'aurai encore quelque chose d'autre)
Sans oublier, en plus que le simple fait d'écrire = au lieu de == risque de provoquer des catastrophes.
Ca fait cher payé pour un caractère oublié, non :question:
Cela ne rend, effectivement, pas l'interface plus complexe, mais, encore une fois,à quel prix :question:Citation:
Des concepts comme les propriétés permettent de rendre les classes plus "intelligentes", sans pour autant complexifier leur interface publique, ni les rendre spécialement moins performantes, ni spécialement plus longues à écrire. Pour ma part, je trouve que ça va dans le bon sens, étant donné que développeur comme utilisateur y gagnent.
Je suis désolé, mais c'est un prix que je ne suis pas près à payer si je peux l'éviter...
- Cela rend plus complexe le fait de déterminer exactement le comportement que l'on met en oeuvre en fonction du "contexte"
- Cela risque d'entrainer des catastrophes si, d'aventure, on utilise le "mauvais contexte"
- Cela ne rend pas forcément la lecture et la compréhension plus facile
Ne t'en fais pas pour ca... je suis connu pour la longueur de mes posts :DCitation:
Bon, désolé du pavé, mais t'es au moins "aussi pire" que moi au niveau longueur des posts... ;)
Tu peux lire/écrire dans une map via [], simplement ça jette une exception si l'élément n'existe pas (en lecture), et ça ne dit rien si tu l'écrases un élément existant (en écriture).
Via find, tu peux vérifier l'absence de l'élément lorsque tu le cherches, sans produire d'exception. Via find + insert, tu peux ne pas écraser un élément existant. C'est donc plus "fin" en terme de possibilités, mais ce n'est pas toujours utile d'aller aussi loin.
Donc, d'un côté, tu as un cas d'utilisation dans ce genre :De l'autre, tu as :Code:
1
2
3
4
5 CmyMap::iterator iter_l = myMap.find(KeyData); if (iter_l!=myMap.end()) PairValue = iter_l->second ; else throw std::range_error("......");
Les deux sont tout à fait équivalents... Le second est quand même nettement plus lisible.Code:PairValue = myMap[KeyData] ;
c'est quoi un CMyMap???
parce que sinon, non, ca ne fait pas ca du tout
operator[k] renvoie une référence sur la valeur associée a la clé k, si la valeur n'existe pas alors une valeur par défaut est d'abord insérée puis renvoyée.
il n'y a jamais besoin d'utiliser find() pour insérer, find c'est pour trouver, pas pour insérer.
insert() insère l'élément (le couple clé-valeur) si il n'existe pas, et renvoie le résultat
pas d'exception, non...
Diantre ! Donc, dans certains cas, on arrive à une expressivité que certains, en se basant principalement sur une définition approximative de la notion d'expressivité, jugent vaguement similaires :mrgreen:
Il convient, lorsqu'on présente des contre-arguments à une argumentation, de bien comprendre les mots que l'autre a utilisé. Je ne dis pas ça pour être agressif ou quoi que ce soit. Il m'arrive de passer du temps à choisir mes mots - et pour ceux que j'ai utilisé, notamment "cohérence" (absence de contradiction) "expressivité" (voir ci-dessous) et "lisibilité" (qualité de ce qui est aisé à lire, à déchiffrer), j'ai eu la grandiose idée de vérifier leur sens exact dans un dictionnaire avant de poster (pour ceux qui on un iPhone, je recommande l'application Littré, qui est gratuite), notamment parce qu'on a beau les utiliser régulièrement, ça ne veut pas dire qu'on les utilise à bon escient (et quand je dis "on", je me mets dans le lot). C'est entre autre la raison pour laquelle mon post est arrivé si tard dans la discussion (16h38 alors que j'ai commencé de l'écrire à 13h15). Ce post même a été commencé à 17h20 environ - mais comme j'estime que j'ai le devoir de vérifier que mon argumentation tiens la route, je mets un peu de temps pour l'écrire.
Etre expressif ne signifie pas "faire beaucoup en peu". Etre expressif, selon le Littré, signifie "qui a la vertu de bien exprimer" - autrement dit, qui est clair - pas forcément concis. Wiktionnaire en rajoute une couche : qui exprimer bien la pensée, le sentiment.
Personne (enfin, pas un programmeur qui a un tant soit peu connaissance de la syntaxe C-like orienté objet, type C++, C# ou Java) ne peut prétendre ne pas comprendre ce que fait ta dernière version - qui est certes plus verbeuse mais qui a le mérite de décrire avec précision ce qu'elle fait.
Les deux versions que tu présentes par ailleurs nécessitent par contre de savoir entre autre que changer la propriété taille change effectivement la taille du tableau (ce n'est dit nulle part, et ce n'est absolument pas implicite ; si on arrive à penser que c'est effectivement ce qui se passe, c'est plus une question d'instinct que de connaissance. Relire "One story, two rules, and a BSP renderer" de M. Abrash (PDF) pour quelques leçons sur les effets de l'utilisation de l'instinct dans le métier de développeur. En tout état de cause, on ne peut pas prétendre que ce code est expressif, puisqu'il ne dit pas ce qu'il fait).
Dans ce cadre, je peux me permettre de ne pas être en accord avec toi : non, l'utilisation d'une propriété en lieu et place de l'utilisation de fonctions nommées spécifiquement pour effectuer une tâche particulière n'est pas suffisamment expressif.
A noter que System.ArrayList<> du framework .Net ne contient pas de propriété accessible en écriture et permettant de modifier la taille du tableau (la taille n'est modifiable que par l'utilisation de Add() et Insert()). La propriété Count est une propriété en lecture seule - je ne vois pas en quoi elle diffère de std::vector<>::size() et ce qu'elle apporte en plus...
Je reviens en outre sur ça :
Parce que tableau.size += 4 ne va pas faire ça du tout. Ca va probablement créer les nouveaux objets du tableau en utilisant des molécules présentes dans l'air ambiant, je suppose :mrgreen:Citation:
Qui plus est en C++ je vais évidemment lire la doc vu que resize appelle un constructeur par défaut sur chaque nouvel élément alloué, et que ca a des implications...
Etant donné que le problème de COBOL est sur la lisibilité, pas sur l'expressivité, tu peux quand même faire du COBOL et rester expressif. "Fort heureusement", tu peux aussi utiliser des propriétés en COBOL (version en cours de standardisation). Un exemple ici. Comme quoi ce langage évolue dans le bon sens...Citation:
Si je n'avais rien à faire de l'expressivité, j'écrirais du COBOL.
J'ai bien aimé cet exemple, mais en fait on peut éviter cette erreur-là en renvoyant void depuis les mutateurs. Du coup, ca ne peux pas arriver.Citation:
Sans oublier, en plus que le simple fait d'écrire = au lieu de == risque de provoquer des catastrophes.
Ca à l'air de marcher en C++ aussi, tu peux renvoyer void avec l'operator=.
Testé sous GCC, le if est refusé.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 class A { public: int x; A(int _x) : x(_x) { } void operator=(const A& a) { return *this = a; (EDIT) en fait pas besoin de ce return } };
Code:
1
2
3
4
5
6
7
8
9
10
11 int main(int argc, char* argv[]) { A a(1); A b(1); if (a = b) { // detection de l'erreur à la compilation } }
Tu le fais exprès... un std::map<typequelconque1,typequelconque2>, c'est si difficile que ça à comprendre ?
Autant pour moi : je viens de vérifier, c'est une vieille STL que l'on a sur une cible qui jette une exception si la clé n'existe pas. Ce qui ne change pas fondamentalement le principe : utiliser [] est nettement plus court que de se fader un find et son itérateur...
Et sans find (ou []), tu fais comment pour ne PAS écraser une valeur existante ??
@Koala : je te réponds un peu plus tard.
insert fait ce qu'il dit : il insère, point barre. il n'ecrase pas.
Et cela montre bien ce que je voulais dire : operator[] est plus concis mais tu ne sais pas ce qu'il fait, tu t'étais planter, d'où l'importance d'un nom correct de fonction.
c'est le point que l'on voulait mettre en avant, emmanuel, koala et moi.
Mais, encore une fois, tu change la sémantique d'un opérateur...
operateur = est, par définition, l'opérateur d'affectation, et, en tant que tel, son prototype doit... renvoyer une référence sur l'objet en cours...
[EDIT]C'est bizare, mais, je fonctionne aussi avec gcc et, si j'essaye de donner un type de retour void à mon opérateur =, j'ai un joli
[/EDIT]Code:D:\projects\TestC++\main.cpp|10|error: return-statement with a value, in function returning 'void'|
En plus, tu te goure durement dans l'implémentation que tu en donnes...
Je ne te disais pas de l'utiliser, mais de le regarder. Je trouve qu'i lpossède un niveau d'organisation du code qui va plus loin que ce que j'ai vu avec la VCL, les winforms, Qt (je n'ai pas ragardé les versions récentes)... et qu'il permet une productivité dans la flexibilité comme dans l'usage de base que ces produits n'offrent pas.
Maintenant, s'il pouvait exister un framework genre wpf en C++ (bien que tu exagères les difficultés de debug, la barrière managé/natif étant assez transparente de ce point de vue là), je m'y pencherais sûrement.
Bravo pour les références au Littré et Wiktionnaire, il est dommage que le contenu qui suit soit un peu décevant.
Quelle précision a t'elle en plus ?Citation:
Etre expressif ne signifie pas "faire beaucoup en peu". Etre expressif, selon le Littré, signifie "qui a la vertu de bien exprimer" - autrement dit, qui est clair - pas forcément concis. Wiktionnaire en rajoute une couche : qui exprimer bien la pensée, le sentiment.
Personne (enfin, pas un programmeur qui a un tant soit peu connaissance de la syntaxe C-like orienté objet, type C++, C# ou Java) ne peut prétendre ne pas comprendre ce que fait ta dernière version - qui est certes plus verbeuse mais qui a le mérite de décrire avec précision ce qu'elle fait.
Pour quelqu'un qui connait le D, les deux premières sont aussi compréhensibles et complètes que la troisième pour quelqu'un qui fait du C++.
Ce que tu veux dire, c'est que la troisième est plus expressive car c'est du C++ et que tout le monde connait le C++. Deux poids, deux mesures.
En fait j'avais écris cet exemple dans un contexte D, où le problème ne se poserait pas. Mais oui, une propriété C++ ca ferait pareil que resize.
Ce que je voulais dire:
En C++, je peux tomber sur un constructeur par défaut redéfini qui lancerait des exceptions pour échouer... (c'est un scénario catastrophe évidemment)
A mon avis ils ont fait ça car utiliser la propriété Capacity est une meilleure idée dans ce cas (qui elle est RW).Citation:
A noter que System.ArrayList<> du framework .Net ne contient pas de propriété accessible en écriture et permettant de modifier la taille du tableau (la taille n'est modifiable que par l'utilisation de Add() et Insert()). La propriété Count est une propriété en lecture seule - je ne vois pas en quoi elle diffère de std::vector<>::size() et ce qu'elle apporte en plus...