Citation:
Envoyé par
Nemek
J'ai hélàs pas d'expérience en C++. Cependant j'émet un doute sur la portabilité des .o, .so et .dll.
Concernant ces bibliothèques, elles sont codées avec des instructions préprocesseurs ou elles utilisent des .h génériques couplés à des .cpp spécifiques ou autre ? Parce que dans ce cas, soit tu considères que tu as déportés le problème, soit tu considères que tu utilises une VM ...
Il faut avouer que ce n'est, effectivement pas du "compile once, run everywhere" mais il est tout à fait possible d'arriver à une compatibilité parfaite en s'astreignant à des règles de codage simplement "correctes".
Il ne faut pas oublier que tu n'arriveras de toutes manières pas non plus à faire tourner la version "windows" de la JVM dans un environnement *nux !!
Mais, par contre, je suis actuellement sur un projet utilisant près de 2000 classes, dont une partie est écrite exclusivement en C++ "standard" (avec un peu de boost cependant :aie:) et dont une autre partie utilise Qt .
Je peux t'assurer que l'on compte les endroits où l'on a recours à la compilation conditionnelle (par exemple pour déterminer le système sur lequel on travaille) sur les doigts d'une main, et que cela n'a rien à voir avec l'utilisation de bibliothèque, mais plutot avec l'impossibilité de trouver une application particulière ( excel pour ne pas la citer) sur des machines *nux ;)
Citation:
Ce n'est pas pour rien qu'on parle de langage semi-interprété ou semi-compilé (c'est comme le verre à moitié plein, à moitié vide).
Ensuite les langages interprétés n'ont pas vérifications statiques du code dans sa globalité et de pré-transformation pour accélérer la transformation en code binaire à l'exécution. Ces étapes sont effectuées à chaque exécution du programme et perdues à la fin de celui-ci. Ce qui conduit à ce qu'une ligne de code soit toute pourrie mais jamais contrôler tant qu'elle n'est pas exécutée.
j'ai presque envie de dire "mais pourquoi faire le travail à moitié :question:"
Bon, c'est très mauvais... :dehors:
Citation:
Ensuite en Java on est très adepte des librairies tierces, et ne pas avoir à les recompiler à chaque fois est une chose non négligeable.
En C++ aussi on est adepte des bibliothèques (hé oui, la traduction de library, c'est... bibliothèque :D ) tierces ;)
Et ce n'est pas cela que l'on recompile à chaque fois ;)
Citation:
En Java j'ai rarement ressenti un tel besoin
Tant mieux pour toi ;)
Citation:
qui d'ailleurs tend faire à confondre héritage et composition.
Le fait est que, bien souvent, "l'héritage mal conçu" (comprend: ne respectant pas LSP ;) ) serait souvent bien avantageusement remplacé par une composition :aie:
Mais l'énorme avantage du NVI est qu'il permet de fournir des "points de variation" de manière particulièrement fine...
Cela permet de respecter d'avantage encore un conseil issu de l'XP: DRY (Don't repeat Yourself ;)) en évitant, justement, d'obliger la personne qui veut dériver une de nos classes à devoir... reprendre le code de la classe parente pour les parties de variant pas ;)
Citation:
Mais j'avoue en avoir parfois ressenti le besoin.
Par ailleurs, il y un pattern assez connu pour contourner le problème en utilisant la composition. Ensuite preuve de ce manque, Java a vu émergé les annotations et la programmation par aspet.
Il s'agit en fait d'injecter de l'appel à du code factorisé dans un coin directement dans les classes semi-compilées. Ce qui revient à automatiser le premier contournement dont je faisais mention.
Donc il existe bien un contournement, certes pas élégant à ce problème.
Si tu le reconnais toi même...
Mais j'irais même plus loin: ca rend, pour moi, l'utilisation d'un bazooka obligatoire pour chasser une mouche :aie: Pas vrai :question:
Citation:
D'ailleurs, il serait intéressant de voir comment les autres langages qui ont fait le même choix s'en sortent.
Surement de manière similaire :aie:
Citation:
Merci pour ce petit cours :) J'aurais bien aimé que mes profs me présentent les choses de cette manière ; ils n'ont jamais évoqué le Liskov.
Pourquoi l'aurait il évoqué dans un cours de java, alors que tout dans java tend à minimiser l'importance de ce principe, alors que c'est (en réalité) le premier de tous :question:
Citation:
Si je résume bien, cela dit qu'un contrat fixé par un type est valable pour tous les sous-types ?
Ou du moins que la marge de manoeuvre des sous-types concernant le contrat est fortement limitée:
- Les préconditions ne peuvent pas être renforcées dans le sous type
- Les postconditions ne peuvent pas être allégées dans le sous type
- Les invariants du type de base doivent etre respectés dans le sous type.
Citation:
Dommage et heureusement que la programmation par contrat (pré-condition/post-condition) n'existe pas vraiment en Java ... Ca m'arrive de contourer ce principe :) Mais c'est peut-etre à cause de code mal fichu à la base ...
Il y a des chances que ce soit à cause de cela, en effet :aie:
Mais, à ta décharge, cela peut remonter vraiment haut, comme à l'obligation qui t'est faite d'hériter, en toutes circonstances, de la classe Object.
Pour ma part, je trouve quelque part presque dommage que la programmation par contrat ne soit pas plus mise en avant... Cela inciterait les gens à se poser les bonnes questions avant de décider qu'il est plus intéressant de recourir à l'héritage là où tout ce qui les intéresse, c'est de récupérer du code, sans être attentif à la "sémantique" de leur classe.
Citation:
Tu as un concept de fonction trigo, non ? Donc d'un point de vue OO, un type "Fonction trigonométrique".
Est-ce une raison pour être obligé de les mettre dans une classe, quand on sait que toutes ces fonctions ont de grandes chances de devoir être statiques car pouvant potentiellement ne dépendre d'aucune instance de la classe :question (et tu sais tout le bien que je pense des fonctions statiques)
Citation:
Oui, via son wrapper Integer.
Le choix d'utiliser Object (notemment pour les méthodes get et delete) sont là pour éviter les cast qui ne sont pas nécessaires ; et qui peuvent être couteux (type checking) si on sait d'un objet tel qu'une map peut être souvent sollicité dans un programme. Encore et toujours affaire de pragmatisme.
Si tu prends le cas d'une Reference Map, elle permet de créer une association non pas entre des clés mais bel et bien des instances (comprendre que la clé c'est l'adresse mémoire). Dans ce cas tu te retrouves bien avec des clés totalement hétérogène. Le but de toutes façons n'est pas d'avoir des clés hétérogènes mais de traiter les Hash map (quelque soit la nature de leurs clés) de manière unique. De la généricité quoi.
Le fait est que je peux concevoir que, dans un domaine particulier, l'on puisse avoir besoin d'une map avec des valeurs "particulièrement hétérogènes", et même que l'on puisse avoir besoin de clé "pas tout à fait identiques" (mais cependant "relativement proches")
Par contre, ce que j'ai beaucoup de mal à accepter, l'aspect systématique de la chose qui apparait en java: même si l'on se dit qu'une map va utiliser le type X comme clé et les types dérivant de Y comme valeur, on n'est jamais à l'abri de l'éventualité que quelqu'un, quelque part dans le code, n'ait pas saisi correctement l'utilisation qui est faite de la map et décide d'utiliser le type A comme clé et le type B commme valeur (A et B n'ayant définitivement rien à voir avec X et Y, tu l'auras compris ;)), surtout si "pour ne pas te casser la tête", tu as décidé (car tu as le droit de le faire :D ) d'accepter que la fonction qui s'occupera d'ajouter les élément à ta map prennent des références sur Object tant pour la clé que pour la valeur :aie:
Tu peux te mettre à l'abri de ce genre d'erreur par programmation, de préférence en choisissant le type correct pour les arguments, au pire, en vérifiant le type réel des arguments que tu obtiens dans la fonction d'insertion, mais il est "dommage, dirons nous" qu'un langage qui se prétend facile et sécurisant permette qu'un mauvais choix d'un coté et une mauvaise compréhension de l'autre permette à des catastrophes de survenir :aie:
Si tu supprimes la super classe et que tu garde une série de hiérarchie clairement distinctes (et "fatalement" de taille limitée ;)), la possibilité de faire ce genre d'erreur se réduit de plus en plus, surtout si toutes tes hiérarchies de classes respectent scrupuleusement LSP ;)
Citation:
J'avoue que les generics c'est un peu bidon mais pour te rassurer il existe les "checked collections". Et les "templates" ne sont pas la panacée car chaque "type" regénère du binaire supplémentaire.
Tu as, effectivement, du code binaire qui est généré pour chaque type spécialisant un template...
Mais c'est normal ;)
Après tout, quand on écrit en C++ une classe proche de
Code:
1 2 3 4 5
|
template <typename Type>
c lass MaClass
{
}; |
et qu'on l'utilise avec les types A, B, et C (tous les trois étant différent ;)) c'est, au niveau du binaire généré comme si on avait écrit les trois classes (la répétition de code en moins)
Code:
1 2 3 4 5 6 7 8 9
| class MaClassA // cas particulier utilisant le type A
{
};
class MaClassB // cas particulier utilisant le type B
{
};
class MaClasseC // cas particulier utilisant le type C
{
}; |
Citation:
Et une question, deux "type" d'un même template avec les mêmes paramètres génériques sont-ils substituables ?
oui:
std::list <int> est substituable à std::list<int> ;)
Citation:
En gros est-ce que deux déclarations de List d'entier sont substituables ?
Si le type d'entier (int Vs unsigned int, short Vs unsigned short), oui, bien sur :D
Citation:
Si oui, est-ce toujours vrai pour une Liste de voiture et une liste de véhicule ?
Non...
C'est pour cela que les collections de la stl n'ont pas vocation à être dérivées ;)
Par contre, tu peux effectivement créer une liste de pointeurs sur Base et insérer un pointeur sur Derivee si Derivee hérite effectivement de Base ;)
Mais attention, nous sommes là dans un paradigme qui n'a rien à voir avec le paradigme OO, nous abordons le troisième paradigme utilisé par C++ qui est le paradigme générique (celui ou l'on ne sait pas encore le type de valeurs que l'on va utiliser, mais ou l'on sait par contre comment on va comment on va les utiliser ;) )
Citation:
Concernant la vérification à la compilation elle n'est valable que si tes listes sont bien spécifiques mais dès que tu en veux des génériques ... T'as le même problème.
Justement pas!!!
Bien au contraire :D
Chaque fois que tu vas instancier une classe template avec un type particulier, le binaire correspondant va être généré (en fait, le code de ta classe va être compilé en considérant que l'on utilise le type envisagé).
Si tu instancie ta classe template avec dix types différent, les vérifications effectuées à la compilation seront effectuées... dix fois (une fois pour chaque type avec lequel ta classe template est instanciée) :aie:
C'est d'ailleurs ce qui ralenti (à notre grand dam) tellement la compilation ;)
Citation:
Pourquoi ne pas le faire si c'est nécessaire ?
Si je prends une classe assez con : les ensembles. Ton idée ca serait de faire une classe SetElement ? Idem pour les clés d'une map avec MapKey ? Dans ce cas, je pense que finalement Java n'a pas tant de wrapper que ça ...
L'idée de concept est en fait différente (et a été écartée afin de permettre une sortie "relativement rapide" de la dernière norme en date, pour tout dire ;))
L'idée est de se dire "voilà, j'ai besoin d'un concept X" (mettons : le concept de "sérialisabilité" ;))
et de continuer "Pour qu'un classe respecte ce concept, il faut qu'elle expose certaines fonctions dont le nom et la signature sont clairement identifiés" ( les fonctions write et read, par exemple, pour le concept de sérialisabilité ;))
Pour terminer par "je vais donc vérifier (à la compilation :D) que tout objet qui me sera transmis ici dispose bel et bien des fonctions ad-hoc"
La notion de concept permet donc une vérification "orthogonale" de ton projet car il n'est pas du tout impossible que tu aies une classe SerializableX qui hérite de X ainsi qu'un autre SerializableY qui hérite de Y (mais sans qu'il n'y ait le moindre lien entre la classe X et la classe Y ;) :D ) dont le seul point commun est d'exposer un certain nombre de fonctions portant le même nom et acceptant un nombre de paramètres identique
Citation:
Bah s'ils ont pas de racine commune tu te retrouves ni plus, ni moins qu'avec des void* ! Mais sans comportement commun.
Ben non, pourquoi :question:
Les DP bridge, Facade et consors, cela ne te dit rien :question:
Et, dans le pire des cas, on peut parfaitement envisager la généricité avec la spécialisation partielle ou totale (pourquoi pas avec un brin de type erasure ) ;)
Mais une chose est sure : jamais, au grand jamais nous n'utiliserons des void * :D
Citation:
Je suis un développeur C++ contrarié et je suis preneur d'un point de vue honnête, complet, sérieux sur ce langage qui me semble bien mieux que Java mais dont hélàs les opportunités sont plus réduites et que mon parcours professionnel témoigne énormément en ce sens (un peu plus d'un an en Cobol et environ 3 en Java).
Ce qu'il faut comprendre, c'est qu'aucun langage n'est foncièrement mauvais ni foncièrement bon!!!
On peut éventuellement dire qu'un langage sera plus adapté à un secteur qu'à un autre, mais il faut vraiment voir les besoins pour parler de la sorte ;)
Et malgré tout le mal que j'ai pu dire de java sur mes dernières interventions, je peux te rassurer : je ne le trouve pas forcément pire qu'un autre (ni forcément meilleur d'ailleurs :D )
Cependant, je me demande quelle est la proportion de projets (quel que soit le langage envisagé ;) ) pour lesquels le choix du langage est basé uniquement sur "ce que connait l'initiateur du projet", sur "l'effet de mode" ou sur le fait que c'est, définitivement, le langage le plus adapté aux besoins que l'on doit rencontrer (ou peut etre sur "la tchatche du commercial qui vend le projet :D) ...
Je reste cependant convaincu que le choix d'un langage "de prédilection" reste une affaire de gout et de philosophie personnelle, mais qu'il est malgré tout quasiment indispensable de s'intéresser au moins un tout petit peu aux langages que l'on "apprécie moins", ne serait-ce que pour pouvoir se "dépatouiller avec" en cas de besoin ;)