@Bousk: Le POD a de meilleures performances et est lui-même plus simple, mais au prix d'avoir une sémantique de valeur incomplète, puisqu'il peut être modifié comme une entité.
Version imprimable
@Bousk: Le POD a de meilleures performances et est lui-même plus simple, mais au prix d'avoir une sémantique de valeur incomplète, puisqu'il peut être modifié comme une entité.
Et la, on est deja aux limites du C++ : pas de mot cle restrict. Mais meme en encapsulant un pointeur restrict dans un pointeur intelligent du type scoped_array, les compilateurs ont du mal. Donc je ne pense pas qu'on soit en mesure d'qvoir une structure meme simple.
Non, pas forcement vrai. Meme dans le cas de choses complexes, dans le monde du calcul, on procede tres rarement ainsi. Dans tous les codes de calcul que j'ai ecrit, je n'ai que du POD ou du POD encapsule dans un pointeur intelligent et que j'utilise apres comme du POD. Je partage a 100% l'avis de Joel car c'est l'approche usuelle dans le domaine HPC.
Dommage que Laurent Gomilla ne traine plus sur dvp, j'aurais été curieux d'avoir son avis sur la question. Sa lib est à cheval entre les deux: contraintes de perf importantes d'une part pour les calculs graphiques, et contraintes de modélisation (objet) d'autre part, car il s'agit d'une lib dont le but est d'être simple d'utilisation et robuste.
Et la doc 2.0 est strictement identique pour ces points.
http://www.sfml-dev.org/documentatio..._1_1Sprite.php
http://www.sfml-dev.org/documentatio...1_1Vector2.php
:calim2:
L'interdiction de modification doit venir du fait qu'on l'utilise de manière const, et non camouflé derrière la présence de membres getter const uniquement.Citation:
une sémantique de valeur incomplète, puisqu'il peut être modifié comme une entité
Si je veux créer une méthode externe qui modifie la position, je veux en avoir le pouvoir sans fioriture. Je passe ma position par référence et c'est réglé.
Là non seulement l'utilisateur peut mentir à son application, puisque passer sa position par référence non const, seules des méthodes const existent.
Mais en plus cette utilisation est doublement source d'erreur et de mauvaise habitude pour lui, puisqu'il a finalement la possibilité de modifier sa position par simple assignation d'une nouvelle position. Vous n'interdisez pas l'opérateur d'assignation n'est-ce pas ? Ni le constructeur par copie ? Sinon vous devez déclarer friend toute classe qui devra manipuler une Position ? :aie:
Pour empêcher ce 2° "problème", il va alors devoir enfin passer par des références constantes, et donc la présence de getter const n'apporte rien de plus qu'un accès direct aux membres, et la "protection : il ne peut pas modifier sa position" est déjà présente.
La plus-value initiale est donc caduque.
Ou alors vous manipulez uniquement des copies... bof bof.
Mais dans ce cas, on ne peut plus l'affecter, le remplacer par une nouvelle valeur.
C'est justement le principe: L'affectation remplace la valeur par une nouvelle, donc c'est normal qu'elle soit autorisée.Citation:
Si je veux créer une méthode externe qui modifie la position, je veux en avoir le pouvoir sans fioriture. Je passe ma position par référence et c'est réglé.
Là non seulement l'utilisateur peut mentir à son application, puisque passer sa position par référence non const, seules des méthodes const existent.
Mais en plus cette utilisation est doublement source d'erreur et de mauvaise habitude pour lui, puisqu'il a finalement la possibilité de modifier sa position par simple assignation d'une nouvelle position. Vous n'interdisez pas l'opérateur d'assignation n'est-ce pas ? Ni le constructeur par copie ? Sinon vous devez déclarer friend toute classe qui devra manipuler une Position ? :aie:
La plus-value est qu'on ne peut pas modifier la valeur comme une entité. On ne peut que la remplacer, l'écraser complètement par une autre. Pas de modification partielle.Citation:
Pour empêcher ce 2° "problème", il va alors devoir enfin passer par des références constantes, et donc la présence de getter const n'apporte rien de plus qu'un accès direct aux membres, et la "protection : il ne peut pas modifier sa position" est déjà présente.
La plus-value initiale est donc caduque.
Ou alors vous manipulez uniquement des copies... bof bof.
Devoir créer une nouvelle Position, puis l'affecter par l'opération = pour finalement remplacer la position initiale...
Quand tu te rends comptes que tu feras en général m_position = Position(new_x, m_position.getY()); c'est verbeux au possible et n'apporte... rien ? C'est bien ça, strictement rien.
Enfin si, ça apporte du temps d'exécution supplémentaire. Eventuellement inliné et réduit.
La lisibilité n'est est pas améliorée. La sémantique pas plus également.
m_position.x = new_x; est simple, rapide, on ne peut plus clair pour l'utilisateur.
Je suis pour le distingo des sémantiques de valeur et entité en général, mais dans ce cas présent ça n'apporte rien du tout.
Mais HPC est un domaine assez particulier. J'imagine par exemple que vous n'utilisez jamais de smart pointers.
Enfin c'est pour dire qu'AMHA, toutes ces règles, idiomes et bonnes pratiques sont valables dans le cas général, donc pas toujours. Et finalement, elles deviennent caduques une fois qu'on les a comprise.
Tu cites exactement la phrase ou je dis que j'utilise des pointeurs intelligents (en fait, je n'ai que ca) :aie:
Dans un univers (réel) 3D je te l'accorde, ça serait plus pratique... comme je l'ai dit dans un de mes premiers posts, les mecs qui bossent sur HPC ou sur embeeded system, je les comprends, mais c'est de la déformation professionnelle de programmer sous contrainte hyper réduite quand tu peux te permettre le zèle de concevoir des trucs plus robustes, même pour un cout plus important en performance. Apres si on veut pousser le bouchon plus loin, on peut laisser tomber les moteurs 3D existants et puis engager tous les types de Future Crew pour faire un jeu 3D moderne...
Je ne dis pas qu'il faut pousser ce raisonnement dans l'autre extrême, ce n'est pas parce qu'on a plus de mémoire et que nos processeurs peuvent faire 1000 fois plus qu'avant qu'il faut se dire "c'est bon je m'en fout des histoire de performance", mais si ton environnement et tes contraintes t'autorisent plus que ce que tu ne t'autorise habituellement, autant en profiter pour prendre en compte des aspects de la programmation qui sont souvent déconsidérés, comme la maintenabilité, la réutilisabilité...
Quelqu'un a parlé de Laurent, et de ce qu'il penserait, et bien pour avoir déjà eu pas mal de conversation de ce style là (et oui je prône les services alors que j'ai toujours pensé donnée) avec lui, sans vouloir parler en son nom, il est plutôt pro-service ; c'est d'ailleurs la dernière conversation que j'ai eu avec lui à ce sujet qui a convertit mon esprit influençable à la notion de service (influençable... en même temps quand quelqu'un comme Laurent vous dit quelque chose vous avez juste envie de boire ses paroles :) ).
Edit : voici une des conversations. => (le sujet n'est pas tout à fait le même, mais on peut voir qu'il préfère la conception pensée en terme de service)
Excusez mon intrusion,
A travers mon regard très amateur, il me semble plus facile :
-D'avoir TOUT le temps une fonction pour accéder aux membres d'un objet.
-De ne pas avoir à penser à mettre const lors de chacune des instanciations d'une structure. (l'objet n'est lui, écrit qu'une fois).
Tout depend le type de programmation derriere. Dans mon cas, j'ai les deux. J'ai une appli qui est bourrine, vectorisee, mais en fin de compte, la structure est bien pensee pour l'evolution dans le domaine. S'il avait fallu passer par des accesseurs, je pense que j'aurai perdu 50 a 90% de la perf de l'application (en passant de debug a optimise, je gagne un facteur 10). Sachant que dans mon domaine, on fait la chasse aux 10% de gain !
L'autre est orientee objet, avec des tableaux qui passent d'un processus a un autre, d'une instance a une autre. Elle est bien plus souple (meme si la premiere version non industrielle ne l'etait pas), et la vectorisation a ete faite avec boost.simd. On aurait pu faire des accesseurs pour recuperer les donnees, et je me suis pose la question (ca aurait permis de calculer a la volee certaines proprietes plutot que de les charger), mais ca ne changerait rien au probleme. Oui, ca me permettrait potentiellement de cacher les details pour ajouter des thread-storage, changer l'ordre de stockage...
Martin Fowler dit qu'il ne faut pas faire de l'over-engineering. Ca en fait partie (surtout qu'on a les outils pour refactorer un projet en 3 clics de souris !)
Bonsoir,
Personnellement je rejoint assez l'avis d'Emmanuel et Joel : j'aurais tendance à commencer avec un POD (enfin des données membres publiques), et si le besoin s'en fait ressentir (si il est réel, il arrivera assez vite sur un élément aussi simple, AMA).
Pour ce qui est de la pensée service évoqué par Kaamui, pourquoi devrais-t-on associer service à fonction ? Personnellement que ce soit une fonction membre, libre prenant un objet du type considéré en paramètre, une donnée membre, ça reste un service à mes yeux.
Un point qu'à soulevé Koala01 et qui me touche le plus c'est les deux syntaxes différentes entre une fonction membre et une donnée membre, mais c'est plus lié au langage et je ne suis pas convaincu qu'introduire des indirections pour la corriger soit pertinent.
Le second point sur l'éventuelle besoin de changement d'implémentation me touche beaucoup moins dans ce cas. En général je suis d'accord avec ce point et c'est un des intérêts de l'encapsulation (séparer interface / implémentation). Avec une classe aussi simple, j'ai tendance à penser que l'interface et l'implémentation sont confondues, et que je n'ai donc pas de besoin de les séparer.
Dans le cas d'une classe Point, si c'est juste un point au sens d'un couple de réel indépendamment de tout hardware, c'est bien son propre d'offrir deux membres, ni plus ni moins. Si il y a un lien fort avec un besoin hardware comme l'exemple de Koala01 avec son couple dans un tableau C, je vois deux schéma :
- Soit le besoin est au coeur même de la conception et la solution la plus simple est peut-être de directement faire de ce type un typedef sur std::array<2,int> ...
- Soit le besoin n'est pas au coeur même de la conception, et dans ce cas j'ai tendance à penser que la solution la plus propre est de n'introduire aucune dépendance (ni dans un sens, ni dans l'autre) et de faire une "couche" d'adaptation pour coupler la conception même et le hardware.
Si par contre c'est un point au sens géométrique, propre à un espace, là j'aurais tendance à offrir un interface plus complexe qu'un simple couple pour offrir les services nécessaires à des changements de répères, des changements d'implémentaiton, etc. Implémentation qui s'appuiera surement sur une autre classe Point étant simplement un couple.
J'ai aussi vu l'argument d'avoir une sémantique de valeurs immuables, et cependant lorsque l'idée des classes matrices a été évoqué, ce point est passé à la trappe sous prétexte qu'il faut la voir comme un conteneur. Donc un élément de M(2,1) doit avoir une sémantique de valeur immuable et un élément de M(100,100) non ? Quel est la limite alors ? Et sinon un élément de M(1,1) (un int) la syntaxe n'est pas homogène, on mets ça dans une classe et avec une fonction membre const ?
Mah, le C++ s'est affranchi du restrict, sous une forme peu orthodoxe, je le reconnais :)
La supposition d'aliasing des données pointée a disparu avec C99 si je ne m'abuse, même si cette fonctionnalité n'a été implémentée que tardivement dans les compilateurs. La plupart des compilateurs C++ appliquent la même règle, et elle doit être dans C++11 (si elle n'est pas déjà dans C++03).Code:
1
2
3
4
5
6
7
8
9 struct v1 { float x, y, z, w; } ALIGNED(8); struct v2 { float x, y, z, w; } ALIGNED(8); void function(v1 *vec1, v2 *vec2); // le compilateur considère qu'il n'y a pas d'aliasing entre vec1 et vec2 (même si (char*)vec1 == (char*)vec2).
Concrètement, ça permet au compilateur d'optimiser encore un peu plus le code, puisque les dépendances entre les variables sont cassées (en pratique, ça a surtout généré pour moi des bugs tordus, parce que le compilateur peut maintenant réordonner le code autour de certains cast ; cf. http://blog.worldofcoding.com/2010/0...-problems.html).
Je te l'accorde, c'est crade :)
Le compilo C++ "assume no aliasing" par défaut désormais? 8O :vomi:
Ou comment créer des bugs...
En fait, il "suffit", peu ou prou de s'intéresser à la définition (celle que l'on trouve dans n'importe quel dico :D) du type que l'on veut créer.
La définition d'une matrice commence sans doute par "un ensemble de...".
A partir de là, peu importe le nombre d'éléments que va contenir ta matrice, elle aura les caractéristiques d'une collection, à savoir le fait d'être copiable (ou non, en fonction du type d'éléments qu'elle contient), d'être assignable, et de fournir un (ou plusieurs) moyen d'accéder à chacun de ses éléments, dusse-t-elle n'en contenir qu'un seul ;)
Par contre, un point est un repère, et on pourrait sans doute en dire autant des couleurs, d'un montant, et de tout ce que tu peux assimiler à une valeur.
Que ton repère puisse "glisser" sur une seule, sur deux dimension(s) ou sur 10 ne changera rien au fait que, si tu fait glisser le curseur d'une seule des dimensions, tu obtiens un nouveau repère.
l'immuabilité des classes ayant sémantique de valeur ne vient, au final, chaque fois que de cette simple constatation: il n'y a, purement et simplement, pas lieu de fournir une méthode quelconque permettant de modifier les valeurs utilisée par ta classe.
Le résultat est donc que, effectivement, les seules fonctions qui aient un sens sont des fonctions constantes, mais, encore une fois, ce n'est pas parce que les seules fonctions exposées sont constantes qu'il faut décider de faire n'importe quoi du point de vue de la const correctness: tu as un objet qui est, par nature, immuable, mais tu veilles malgré tout à le transmettre sous la forme d'un objet constant quand il ne doit pas être modifié, autrement, bien qu'étant immuable, tu aurais l'occasion d'assigner à ta référence une autre valeur, et de faire potentiellement des dégâts ;)
Je n'ai pas écrit ça sans réfléchir. Pensons en terme de services donc : celui rendu par la classe Position est de contenir les deux coordonnées qui forment une position dans l'espace (quel qu'il soit). Celui rendu par la fonction int Position::x() const est de fournir une coordonnée de cette position. Ça ne peut rien être de plus, sinon tu lui donnes trop de responsabilités. Partant de là, tu écartes donc toute idée de "tests" (suis-je au bord du monde, dois-je inverser y parce que je suis dans un miroir, etc.). Cette fonction ne feras jamais rien d'autre que de retourner la valeur de x. J'espère qu'on est d'accord jusque là.
La seule autre chose que tu peux donc modifier, c'est l'exemple donné par Koala : comment est stockée cette valeur et comment on y accède. Deux variables séparées, un tableau de deux éléments, un pointeur vers une mémoire gérée manuellement, un valarray, ... Et là tu peux effectivement faire des changements dans ta classe qui auront des répercussions dans le code appelant. Mais même alors, pourquoi modifier la classe si un tel besoin se fait sentir ? Revenons aux services : on a besoin d'obtenir la position sous forme d'un valarray. Est-ce à Position de le fournir ? Ou faut-il laisser une fonction libre faire la conversion ?
Je n'ai rien contre l'encapsulation. Mais si je me permets d'intervenir sur ce sujet, c'est que j'en ai déjà écrit plusieurs classes de ce type : des Vector3, des Point2, et j'en passe. Au début, suivant les conseils que j'avais pu lire sur ce forum et ailleurs, qui me disaient que l'encapsulation c'était bien, j'ai sagement caché mes données membres et utilisé des fonctions membres pour y accéder. À ce jour, ça ne m'a jamais servi à rien d'autre que de m'user les doigts. Je souhaite juste éviter ça à l'OP.
Je pense que, quand on en arrive à ce niveau là, il faut se poser la question : qu'est ce qu'on gagne en faisant ça, et qu'est ce qu'on perd. De mon point de vue, le gain est faible (voir nul, cf. plus haut, mais je ne veux pas débattre là dessus), et le coût plus élevé qu'il n'y paraît. Pour une raison discutable (eh oui, on en discute ici même), on se met des boulets aux pieds :
En remplaçant cette classe Position qui fait tant débat par une structure Vector2 dénuée de sémantique (comme dans la SFML), le code est plus simple :Code:
1
2
3 void Tidus::deplacer(const int dx, const int dy) { pos = Position(pos.x() + dx, pos.y() + dy); }
Ce n'est pas une question de performance ici, mais une question de rapidité d'écriture (moins de temps passé à écrire = plus de temps pour réfléchir), de concision (moins de caractères à taper = moins de chance de faire des erreurs) et de clarté (moins de caractères à lire et opérations explicites = code plus rapidement compris).Code:
1
2
3 void Tidus::deplacer(const Vector2& dpos) { pos += dpos; }
Là encore, je n'ai rien contre le code verbeux (je n'utilise jamais de using namespace), mais seulement quand ça apporte quelque chose : que ça lève une ambiguïté pour le lecteur, ou que ça empêche de faire des bêtises. Ici je ne vois ni l'un ni l'autre.
Pour référence, les deux seules bibliothèques que je connais qui traitent ce sujet :
- Ogre::Vector2 (Ogre3D) : membres publics
- sf::Vector2 (SFML 2.0) : membres publics
@koala01: Je pense que tu as mal compris ce que je voulais dire (je parlais de matrice au sens mathématique du terme). Ce passage de mon message était là pour mettre en évidence qu'un point ou même qu'une simple valeur (*) c'est (**) une matrice, alors pourquoi traiter un point, un vecteur, un int, une matrice de manières différentes ?
(*) Je force le trait volontairement pour insister, mais l'idée est quand même là.
(**) C'est loin d'être parfaitement rigoureux, mais on va faire avec : au sein de cette discussion, ce sont bien tous des familles.
PS: Et la définition d'une matrice ne sera jamais "un ensemble de ...".
Seulement dans le cas ou tu manipules des pointeurs vers des types différents. Si tu as deux variables pointeur du même type, alors le compilateur part du principe que l'aliasing est possible. Idem si l'un des type pointé est char*. En C11, l'ajout du mot clef restrict permet de dire au compilateur qu'il n'y a pas d'aliasing même si les données manipulées sont de même type.
(et pour le bug, j'ai déjà subi ; et ça m'a pris deux semaines pour le caractériser et le corriger...:cry:)
En fait je ne suis pas d'accord, pas totalement. Je dirais la même chose que toi + "je vais séparer interface/implémentation, car on ne sais jamais quel service je pourrais avoir besoin qu'elle me rende". Et même s'il y a effectivement peu de chance que cela arrive, c'est possible, et sans que ça vienne en contradiction avec la SRP.
C'est, à mon sens, encore une erreur. L'OP est d'un niveau débutant (je le sais cela fait plusieurs soirs que je l'aide à installer la SFML et à apprendre à s'en servir correctement), et il est largement préférable qu'il s'use les doigts sur les principes fondamentaux de la bonne manière de programmer. Prévoir au maximum ce qu'on ne peut prévoir, commenter, documenter, encapsuler, penser service plutôt que donnée, SRP, OCP, Liskov, Interface Segregation, Dependency Inversion (merci wiki pour le D :mrgreen:)..Citation:
Je n'ai rien contre l'encapsulation. Mais si je me permets d'intervenir sur ce sujet, c'est que j'en ai déjà écrit plusieurs classes de ce type : des Vector3, des Point2, et j'en passe. Au début, suivant les conseils que j'avais pu lire sur ce forum et ailleurs, qui me disaient que l'encapsulation c'était bien, j'ai sagement caché mes données membres et utiliser des fonctions membres pour y accéder. À ce jour, ça ne m'a jamais servi à rien d'autre que de m'user les doigts. Je souhaite juste éviter ça à l'OP.
ça apporte une réponse à de potentiels imprévus ! Moi j'essaie juste de faire passer cette évidence dans cette conversation..Citation:
Là encore, je n'ai rien contre le code verbeux (je n'utilise jamais de using namespace), mais seulement quand ça apporte quelque chose.
Dingue... Si ca se trouve, c'est exactement ce qui vient de m'arriver... Le compilateur Intel ne recharge pas la valeur d'un tableau qui a ete modifie par un autre pointeur et qui fait crasher le programme par la suite... Et avec un display ca passe parce qu'on recharge l'element pointe...
Probable oui, foutu en cache pour optimiser les performance (c'est bien pour ça que le compilo intel est connu =), et après l'avoir testé je ne peux qu'approuver)
En tout cas, ce genre de débat est sympa, ça montre bien que même à haut niveau, tout le monde n'est pas d'accord sur la façon de faire.
Ok je vois. Je pense qu'on peut dire que c'est une démarche prudente. À mon avis elle l'est un peu trop, mais ça me semble n'être qu'une question de goûts finalement, cf. ci-dessous 3ème citation.
Je comprends tout à fait l'objectif. Mais il y a un aspect qu'il faut aussi garder à l'esprit : la motivation. Quand on apprend un langage (ou simplement la programmation dans son ensemble), il est facile de se démotiver, surtout quand on s'attaque à des langages pointus comme le C++. J'ai peur que, en essayant de respecter tous ces principes dès la première heure, et donc d'être amené à écrire du code étrange et lourd comme celui qui a déjà été présenté dans les posts précédents, on finisse par penser que "écrire du C++ c'est pénible, la syntaxe n'est pas naturelle, il y a plein de mots et de caractères inutiles" (inutiles = dans un monde parfait, je n'aurais pas à les écrire).
Alors qu'en codant différemment (i.e. en se permettant de violer explicitement des "règles" comme l'encapsulation, le principe de Demeter, ...), et, il faut l'avouer, surtout depuis l'arrivée du C++11 (en particulier avec auto, les lambdas et les listes d'initialisations, et plus indirectement avec les autres features comme la sémantique de mouvement ou les templates variadiques), je trouve qu'on peut s'approcher très près du "monde parfait" où chaque atome du code a un sens d'abord pour le lecteur plutôt que pour la machine, tout en s'assurant les performances quasi-optimales d'un langage compilé. À mon humble avis, c'est ça qui rend le C++ différent de tous les autres langages, et c'est ce pourquoi je l'apprécie autant.
Maintenant, comme il a déjà été dit plusieurs fois sur ce forum (peut être pas encore sur cette discussion, pardon si c'est le cas), avant d'enfreindre les règles, il faut savoir qu'elles existent, quel est leur but et quand on peut s'en passer. Donc il est clair que plus tôt on se frotte à ces règles, mieux c'est.
Je pense que tout le monde est d'accord là dessus ;) J'ai l'impression que le débat tourne plus autour du seuil de tolérance de chacun quand à l'improbabilité d'un imprévu. Si j'ai bien compris, ton point de vue est que tout imprévu, aussi potentiel et improbable soit-il, est un imprévu, et donc tu fais donc en sorte que ton code soit affecté au minimum si jamais il venait à se produire. De mon point de vue, il y a des imprévus tolérables, et parmi eux, certains dont je juge qu'ils nécessiteraient trop d'effort pour s'en protéger, et je décide donc de privilégier la lisibilité et la simplicité du code.
Autant je suis d'accord avec toi sur le fond du fond, autant je soutiens quand même que bien apprendre, c'est aussi apprendre la simplicité. Deux acronymes importants : YAGNI (you ain't gonna need it) et KISS (keep it simple, stupid).
Le premier (YAGNI), parce que essayer de prévoir des besoins non connus pour plus tard, c'est se tirer une balle dans le pied. Poussé à l’extrême, ça veut dire qu'on ne code jamais, parce que le logiciel peut potentiellement tout faire, hors il est impossible de prévoir l'architecture d'un logiciel qui peut tout faire. Ramené dans des proportions normales, ça veut juste dire qu'on va plus que probablement écrire du code qui ne servira jamais (code mort donc, jamais testé, et source potentielle de problème), ou qui servira mais de façon modifiée par rapport à ce qu'on a déjà écrit (ce qui revient à écrire deux fois le même code).
Le second (KISS), parce que plus un code est complexe, plus il a de chances d'être perclus de bugs. De plus, si le code est intelligent et qu'il est buggé, alors il faudra être deux fois plus intelligent pour le débugger. Donc autant écrire du code bête, ça le ramène à notre portée pour le debuggage (le pire, c'est que je suis très sérieux quand je dis ça).
De plus, lorsqu'on code, il est tout de même relativement facile de voir que certaines données n'ont pas de comportements intrinsèques, ou que l'abstraction générale est plus aisée à comprendre si les traitements sur un type particulier sont extrinsèque à ce type. Dans le cadre de la programmation d'un jeu (2D, 3D...), il sera certainement plus aisé de manipuler une structure vector3d qu'une classe vector3d avec toutes les méthodes possibles et imaginables ; le fait d'avoir une structure peut même être assez bénéfique, dans le sens ou les API graphiques sous-jacentes sont très dépendantes de l'organisation interne de ce type de données (c'est d'ailleur l'API qui est prescriptrice dans ce cas). Après, rien n'empêche de coder quelques fonctions d'aide (méthode statique de création d'un vecteur unitaire...).
Alors là +1.
Je ne vois pas quoi ajouter, pour moi tu as très bien résumé la situation : tout dépend d'où chacun trouve son équilibre entre simplicité et prudence. Je n'utilise jamais les structures personnellement, je considère (c'est vraiment personnel comme point de vue, aucune raison technique) que les structures sont des versions obsolètes des classes, alors même quand je conçois en terme de donnée (ce qui est instinctivement plus naturel pour tout le monde je penses), j'utilise des classes... tout est une question d'habitude finalement.
Alors ça c'est très discutable de mon point de vue. Car poussé à l'extrême dans l'autre sens également, on ne prévois rien, et on va devant de très gros problèmes (typiquement, on avance tant qu'on peut une fois qu'on est coincé on recommence).
Je trouve, encore une fois d'un point de vue personnel, que même si je comprends, je trouve que ce type de raisonnement fait office de véritable frein à l'évolution intellectuelle d'une personne dans le domaine où elle applique ce dit raisonnement.Citation:
Le second (KISS), parce que plus un code est complexe, plus il a de chances d'être perclus de bugs. De plus, si le code est intelligent et qu'il est buggé, alors il faudra être deux fois plus intelligent pour le débugger. Donc autant écrire du code bête, ça le ramène à notre portée pour le debuggage (le pire, c'est que je suis très sérieux quand je dis ça).
Pas compris.Citation:
De plus, lorsqu'on code, il est tout de même relativement facile de voir que certaines données n'ont pas de comportements intrinsèques, ou que l'abstraction générale est plus aisée à comprendre si les traitements sur un type particulier sont extrinsèque à ce type.
Je ne penses pas car une classe vector3d pourra être générique et fournir des opérateurs mathématiques très utiles pour manipuler des vecteurs. Avec une structure c'est plus compliqué je penses.Citation:
Dans le cadre de la programmation d'un jeu (2D, 3D...), il sera certainement plus aisé de manipuler une structure vector3d qu'une classe vector3d avec toutes les méthodes possibles et imaginables ; le fait d'avoir une structure peut même être assez bénéfique, dans le sens ou les API graphiques sous-jacentes sont très dépendantes de l'organisation interne de ce type de données (c'est d'ailleur l'API qui est prescriptrice dans ce cas). Après, rien n'empêche de coder quelques fonctions d'aide (méthode statique de création d'un vecteur unitaire...).
Une structure étant strictement identique à une classe, à la visibilité par défaut près. En quoi une structure serait "plus compliquée" qu'une classe ? :calim2:
Une structure Vector3D a tout son sens, avec les 3 membres x,y,z et des méthodes pour agir sur le vector ou en générer un nouveau, addition etc.
Le fait que ce soit une structure, avec un accès à ses membres xyz direct, n'interdit en rien d'avoir des méthodes pour le manipuler.
Encore une fois, structure ou classe sont strictement identiques. Tout ce qu'une classe peut faire, une structure le fait aussi.
Voir par exemple le Vector3 de Ogre, un Vector3 des plus classiques.
euh.... non surement pas ? Pas de notion de portée dans une structure, pas de notion d'héritage (et qu'on vienne pas me dire "si ! on peut jouer avec les vtables !" :lol:), pas de notion de généricité je crois.
Encore une fois, je ne vois qu'un vecteur de flottants. Ce dont je parle c'est d'un vector3<T> avec T = int, float, etc..Citation:
Tu oublies un point : en C++, les structures telles que le C les défini n'existent pas. Une struct est un classe, avec un contrôle d'accès par défaut différent. Ces deux codes sont strictement équivalents (enfin presque: par défaut, on hérite d'une classe en private et d'une struct en public) :
Code:
1
2
3
4
5
6
7 class toto { int m_titi; public: toto(int titi) : m_titi(titi) { } };
Code:
1
2
3
4
5
6
7 struct toto { toto(int titi) : m_titi(titi) {*} private: int m_titi; };
Code:
1
2
3
4
5 template <class T> struct vector3 { T x, y, z; };
Ah d'accord, je ne savais pas. Même l'héritage est possible avec une struct ?
Mais du coup, si c'est aussi identique qu'une classe, à part le contrôle d'accès par défaut différent, on est en train de faire tout un débat pour savoir quel mot clé chacun préfère utiliser ? Aussi, a quoi cela sert-il d'avoir deux choses aussi identiques, avec deux noms et une règle par défaut différents ? 8O
J'ai loupé quelque chose ou tu dis là une grosse bêtise? (Je prend des précautions car en ce moment, j'en dit beaucoup des grosses bêtises :aie: )
La notion de portée est la même pour les classes et les structures. De même que l'héritage. En fait, une structure EST une classe, seul la visibilité par défaut (public/private) change.
Presque, mais pas tout à fait :)
Le débat est en fait celui-ci: est-ce que toutes les entités (class ou struct) d'un programme (ou librairie) doivent être présentés sous la forme d'objets encapsulés (qui, donc, fournissent une interfaces proposant des services) ou peut-on intégrer dans un programme (ou une librairie) des entités qui ne sont pas encapsulées (et donc, ne fournissent pas de services) ? Si tel est le cas, quelles sont les conditions que doit remplir selon vous une telle entité ?
J'ai l'impression qu'il y a également une discussion parallèle et qui concerne l'interprétation que nous avons de la notion de service. En gros, "est-ce qu'il suffit de faire un accesseur à une donnée pour en faire un service?"
Derrière cela, se pose aussi la question de la classification entité/valeur dans le cadre d'une conception en terme de services. Est-ce qu'une classe à sémantique de valeur peut être implémentée sous forme de service?
Après, je ne comprend pas tout dans cette histoire, donc il est fort possible que mon interprétation du débat soit erronée.