OVH Cloud OVH Cloud

Apprendre le C++

69 réponses
Avatar
leo.hal
Bonjour,

J'ai une petite exp=E9rience en Java et en C et je voudrais me mettre au
c++.

Auriez-vous des sites (a part developpez.com ) ou des livres en
fran=E7ais pour apprendre ce langage.

Merci.

10 réponses

3 4 5 6 7
Avatar
James Kanze
Mathias Gaunard wrote:

je souscris à l'intégralité de ton point.
en fait, ma surprise venait de là: déconseiller une approche pour l ui
préférer quelque chose ne pouvant répondre aux mêmes exigences est
surprenant.


Le polymorphisme peut être utilisé dans presque tous les cas à la p lace
des templates.


Ah, bien ? Et comment est-ce qu'on utilise le polymorphisme
dynamique pour enforcer des invariants sur le typage, lors de la
compilation ?

On n'y perd alors en performance et en type safety.


On peut parfois perdre un peu en performance. C'est rarement
signifiant, mais si le profiler dit que c'est un problème, il
faut y agir. Autrement, ça tombe sur la qualification de
l'optimisation prématurée.

Et je n'ai jamais vue de problème de type safety avec le
polymorphisme dynamique.

C'est pour ça que si on peut utiliser les templates à la place, c'est
conseillé.


Non. Ce qui est conseillé, c'est d'utiliser la technique qui
convient. Et dans les rares cas où les deux conviennent, de
préférer le polymorphisme dynamique, à cause de la souplesse
supplémentaire.

(Ce qui est aussi conseillé, c'est de ne se servir ni de l'un ni
de l'autre sans que ça apporte quelque chose de réel et de
concret. La généricité prématurée est aussi mauvaise que
l'optimisation prématurée, et le code le plus simple qui remplit
le contrat est toujours le meilleur.)

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Mathias Gaunard

Ah, bien ? Et comment est-ce qu'on utilise le polymorphisme
dynamique pour enforcer des invariants sur le typage, lors de la
compilation ?


On ne peut pas, puisqu'on perd en type safety comme je l'ai dit.


On peut parfois perdre un peu en performance. C'est rarement
signifiant, mais si le profiler dit que c'est un problème, il
faut y agir. Autrement, ça tombe sur la qualification de
l'optimisation prématurée.


Cela n'est pas nécessairement de l'optimisation prématurée.
Parfois il faut savoir concevoir directement dès le début de manière la
plus flexible tout en étant le plus performant, car on sait que le
composant est un composant bas-niveau qui sera fort utilisé par le
programme.


Et je n'ai jamais vue de problème de type safety avec le
polymorphisme dynamique.


C'est pourtant tout le principe du truc, puisqu'on passe par un type
abstrait qui peut alors englober toutes les possibilités des types
fournis via dérivation.

On remplace

template<typename T>
void ma_fonction(T& t);

par

void ma_fonction(Object& t);

Et on fait dériver tous les types utilisables avec ma_fonction de Object.

C'est plus ou moins comme ceci que fonctionnent tous les langages
"objet" qui dérivent tous leurs types d'un type abstrait unique.

Bien entendu en C++, l'idéal est de pouvoir réduire l'utilisation de
l'objet à des fonctions particulières qu'on pourra rendre virtuelles.


Pour un vrai exemple, on peut prendre les foncteurs.

template<typename F>
void apply_functor(F &f);

Qu'on peut remplacer par

void apply_functor(function<signature du foncteur>& f);

(en fait l'exemple n'est pas parfait puisqu'ici on perd la possibilité
d'overloading à cause de la signature fixée -- mais bon j'ai pas
d'autres idées où les deux solutions sont vraiment utiles)



Non. Ce qui est conseillé, c'est d'utiliser la technique qui
convient. Et dans les rares cas où les deux conviennent, de
préférer le polymorphisme dynamique, à cause de la souplesse
supplémentaire.


Conseillé par qui ?
Les conseils que j'ai donné sont ceux donnés non seulement par des
membres du comité mais aussi par de grandes entreprises ou fournisseurs
de compilateurs.

Et comme je l'ai déjà dit, quasiment tout ce qu'on peut implémenter avec
les templates peut s'implémenter avec le polymorphisme.
Comment ils feraient sinon les langages objet sans templates pour faire
des choses similaires ? En Java par exemple, les conteneurs sont en fait
des conteneurs de Object, ce qui oblige d'ailleurs à caster quand on
veut utiliser l'objet.


(Ce qui est aussi conseillé, c'est de ne se servir ni de l'un ni
de l'autre sans que ça apporte quelque chose de réel et de
concret. La généricité prématurée est aussi mauvaise que
l'optimisation prématurée, et le code le plus simple qui remplit
le contrat est toujours le meilleur.)


Pourtant l'un des buts de la programmation c'est quand même la
réutilisabilité des composants.
Et pour qu'un composant soit réutilisable, il faut qu'il soit
suffisamment flexible et générique.

Avatar
Loïc Joly
Et comme je l'ai déjà dit, quasiment tout ce qu'on peut implémenter avec
les templates peut s'implémenter avec le polymorphisme.
Comment ils feraient sinon les langages objet sans templates pour faire
des choses similaires ? En Java par exemple, les conteneurs sont en fait
des conteneurs de Object, ce qui oblige d'ailleurs à caster quand on
veut utiliser l'objet.


Je pense que l'on n'est pas d'accord sur le terme "implémentable". Pour
moi, un conteneur typé n'est pas implémentable en Java (sauf
introduction des génériques), alors que pour toi, il est implémentable,
mais il faut caster. Le fait de devoir caster (et le fait aussi que le
contenu doive dériver d'une classe de base) n'est pas pour moi un détail
annexe, mais un aspect fondamental.

Donc, pour moi, dans ce cas, le polymorphisme dynamique ne résoud pas le
problème. Les templates le résolvent.

--
Loïc

Avatar
James Kanze
Mathias Gaunard wrote:

Ah, bien ? Et comment est-ce qu'on utilise le polymorphisme
dynamique pour enforcer des invariants sur le typage, lors de la
compilation ?


On ne peut pas, puisqu'on perd en type safety comme je l'ai dit.


C'est donc que les templates servent un autre but que
l'héritage. J'utilise les templates quand j'ai besoin de définir
des invariants sur le typage. J'utilise l'héritage, la plupart
du temps, précisement quand je veux ouvrir le typage. Dans leurs
utilisations typiques, chez moi au moins, ils jouent des rôles
prèsqu'opposés.

On peut parfois perdre un peu en performance. C'est rarement
signifiant, mais si le profiler dit que c'est un problème, il
faut y agir. Autrement, ça tombe sur la qualification de
l'optimisation prématurée.


Cela n'est pas nécessairement de l'optimisation prématurée.
Parfois il faut savoir concevoir directement dès le début de manièr e la
plus flexible tout en étant le plus performant, car on sait que le
composant est un composant bas-niveau qui sera fort utilisé par le
programme.


C'est à quoi sert l'encapsulation.

Et je n'ai jamais vue de problème de type safety avec le
polymorphisme dynamique.


C'est pourtant tout le principe du truc, puisqu'on passe par un type
abstrait qui peut alors englober toutes les possibilités des types
fournis via dérivation.


Oui. On utilise l'héritage parce qu'on veut permettre plusieurs
types. Quand la généricité passe par le type. Mais que
le client ne doit voir qu'un type. Du point de vue du client,
les templates permettent à plusieurs types distincts de partager
la même implémentation, tandis que l'héritage permet plusieurs
implémentations distinctes d'un seul type.

Les deux choses sont prèsque contraires. Et du coup, c'est
vraiment exceptionnel quand tu as le choix.

On remplace

template<typename T>
void ma_fonction(T& t);

par

void ma_fonction(Object& t);

Et on fait dériver tous les types utilisables avec ma_fonction de Objec t.


Ce qui augumente bien la souplesse.

Sauf qu'évidemment, en C++, on utilise une interface qui définit
le contrat dont a besoin ma_fonction. (Je n'ai jamais trop vue
l'utilité d'une interface commune, du type Object. Je ne vois
pour ainsi dire rien que doit pouvoir faire tous les objets.)

C'est plus ou moins comme ceci que fonctionnent tous les langages
"objet" qui dérivent tous leurs types d'un type abstrait unique.


Je ne le crois pas. C'était le cas de Smalltalk, mais ce n'est
pas comme ça qu'on écrit en Java, ni en C++, ni en n'importe
quel langage OO moderne.

Bien entendu en C++, l'idéal est de pouvoir réduire l'utilisation de
l'objet à des fonctions particulières qu'on pourra rendre virtuelles.


On rend les fonctions virtuelles quand on veut définir un
contrat, sans imposer une implémentation particulière. En fait,
au niveau applicatif, on ne rend les fonctions virtuelles que
quand on a besoin de la généricité. La plupart des types ne sont
ni polymorphique, ni des templates.

Pour un vrai exemple, on peut prendre les foncteurs.

template<typename F>
void apply_functor(F &f);

Qu'on peut remplacer par

void apply_functor(function<signature du foncteur>& f);

(en fait l'exemple n'est pas parfait puisqu'ici on perd la possibilité
d'overloading à cause de la signature fixée -- mais bon j'ai pas
d'autres idées où les deux solutions sont vraiment utiles)


Surtout, tu perds de la souplesse. Pour rien.

Non. Ce qui est conseillé, c'est d'utiliser la technique qui
convient. Et dans les rares cas où les deux conviennent, de
préférer le polymorphisme dynamique, à cause de la souplesse
supplémentaire.


Conseillé par qui ?


Par tous les experts que je connais.

Les conseils que j'ai donné sont ceux donnés non seulement par des
membres du comité mais aussi par de grandes entreprises ou fournisseurs
de compilateurs.


En tant que membre du comité, je peux dire qu'ils ne sont pas
donné par le comité. Ils ne sont pas donnés par des fournisseurs
de compilateurs que je connais non plus. J'aimerais bien voir,
d'ailleurs, qui les donne, vue qu'ils sont manifestement faux.
Tous les experts que je connais utilise et l'héritage, et les
templates. Dans des cas différents, évidemment.

Et comme je l'ai déjà dit, quasiment tout ce qu'on peut
implémenter avec les templates peut s'implémenter avec le
polymorphisme.


Pas du tout. Comment est-ce que tu imposes l'invariant que tous
les éléments dans une collection soit de type T ?

Comment ils feraient sinon les langages objet sans templates pour faire
des choses similaires ? En Java par exemple, les conteneurs sont en fait
des conteneurs de Object, ce qui oblige d'ailleurs à caster quand on
veut utiliser l'objet.


Ce n'est plus vrai ; Java a ajouté des templates. Justement,
parce qu'il y a des choses qu'on ne peut faire avec des
templates, de même qu'il y a des choses qu'on ne peut faire
qu'avec l'héritage.

(Ce qui est aussi conseillé, c'est de ne se servir ni de l'un ni
de l'autre sans que ça apporte quelque chose de réel et de
concret. La généricité prématurée est aussi mauvaise que
l'optimisation prématurée, et le code le plus simple qui remplit
le contrat est toujours le meilleur.)


Pourtant l'un des buts de la programmation c'est quand même la
réutilisabilité des composants.
Et pour qu'un composant soit réutilisable, il faut qu'il soit
suffisamment flexible et générique.


Pour qu'il soit réutilisable, il faut d'abord et avant tout
qu'il marche ; faire marcher un composant est plus facile sans
une généricité inutile. Ensuite, pour être réutilisable, il faut
qu'il y a besoin dans deux cas différents. Quand cette situation
arrive, on rend le composant générique, dans la dimension
nécessaire. L'expérience montre que la généricité, c'est comme
l'optimisation. De même que le programmeur se trompe toujours
sur où se trouve les problèmes de performance, de même il se
trompe toujours sur la question des dimensions où il aurait
besoin de la généricité.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
James Kanze
Loïc Joly wrote:
Et comme je l'ai déjà dit, quasiment tout ce qu'on peut implément er avec
les templates peut s'implémenter avec le polymorphisme.
Comment ils feraient sinon les langages objet sans templates pour faire
des choses similaires ? En Java par exemple, les conteneurs sont en fait
des conteneurs de Object, ce qui oblige d'ailleurs à caster quand on
veut utiliser l'objet.


Je pense que l'on n'est pas d'accord sur le terme "implémentable". Pour
moi, un conteneur typé n'est pas implémentable en Java (sauf
introduction des génériques), alors que pour toi, il est implémenta ble,
mais il faut caster. Le fait de devoir caster (et le fait aussi que le
contenu doive dériver d'une classe de base) n'est pas pour moi un dét ail
annexe, mais un aspect fondamental.


Attention : il n'y a aucun problème d'écrire une collection
type safe, sans conversions de type, en Java. Le problème, c'est
que pour le faire (jusqu'il y a peu), il fallait bien préciser
le type. Il n'y avait pas de façon de dire que la collection
pourrait prendre n'importe quel type, mais qu'une instance
donnée ne prenait que certains types. Ce qui fait que pour
écrire du Java robuste, on est amené à dupliquer beaucoup de
code.

Donc, pour moi, dans ce cas, le polymorphisme dynamique ne
résoud pas le problème. Les templates le résolvent.


Exactement. Il s'agit ici d'enforcer un invariant sur le système
du typage. Le rôle de l'héritage est l'inverse.

Donc, par exemple, dans un monde idéal, les collections
utiliserait l'héritage pour présenter une interface plus ou
moins commune (fonctions begin(), end(), etc.), mais des
templates pour préciser le type contenu. Comme le préconsisait
déjà Barton et Nackman. (Mais il y a d'autres contraints. Par
exemple, les itérateurs STL sont -- comme les pointeurs -- des
valeurs, qui doivent supporter la copie et l'affectation. Du
coup, le polymorphisme dynamique est exclu.)

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Marc Boyer
Le 19-01-2007, Mathias Gaunard a écrit :

Je sais a peut pret parler des mecanismes C++. Ses paradigmes,
ca commence a devenir plus difficile. Differencer les paradigmes clef
des paradigmes secondaire, ca devient plus subtil, et si on commence
a argumenter en utilisant l'obligation de mot-clef, ca devient
trop lourd.


J'utilise pourtant le terme 'clé' depuis le début.


Oui, si tu fais une distinction sémantique entre clé et clef,
j'imagine bien qu'entre paradigme et concept, je vais avoir
du mal à te suivre.


Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)


Avatar
Laurent Deniau
Mathias Gaunard wrote:

Ce code n'a pas de virtual, poutant l'appel f(b) utilise le
polymorphisme "implicite" comme vous le qualifiez. Si vous n'en etes
pas convaincu, changez "struct B : A" en "class B : A" et ca ne
compile plus...


L'OBP c'est l'OOP sans l'héritage, et donc sans le polymorphisme.

Quel est donc l'apport de votre commentaire, si ce n'est dire qu'on n'a
pas besoin des fonctions virtuelles pour voir apparaître certaines
caractéristiques du polymorphisme ?


Mon commentaire etait seulement en reponse au deux passages:

Le fait que les fonctions virtuelles ne soient pas activées par
défaut > montrent bien l'intention de faire de la programmation orientée

objet > une fonctionnalité à part.

Si mes souvenirs sont bons, le DnE (le mien est a la maison) explique
pourquoi virtual n'est pas active par defaut, et les raisons sont
sensees, coherentes et n'ont rien a voir avec la volonte de mettre de
cote le polymorphisme mais plutot un probleme de compatibilite avec le C
(ABI compatible par defaut). Le fait que le destructeur n'est pas
automatiquement virtuel quand une fonction membre l'est est peut-etre
deja plus discutable, mais aujourd'hui les compilateurs emmettent un
avertissement pour cet oubli.

Je vais donc répéter.
Le programmation orientée objet n'est pas un paradigme clé de C++. La
programmation basée objet, si.


Vous affirmez que le C++ n'a pas l'intention d'integrer le polymorphisme
par defaut parce qu'il faut ajouter un mot clef dans le code (critere
totalement arbitraire). Je montre simplement que sans aucun mot clef, le
polymorphisme est deja present dans le systeme de type (critere
probablement plus consequent que la notion d'activation par mot clef).

Ensuite la POO ne se restreint pas au polymorphisme. L'encapsulation
est probablement le premier concept introduit en POO et certain
langages se revendiquent OO a travers leur systeme de module.


L'encapsulation est une fonctionnalité du OBP, pas du OOP.
Ça ne passe pas par l'héritage.


Il est etrange de reduire POO - PBO a l'heritage uniquement.

a+, ld.


Avatar
Loïc Joly
Mathias Gaunard wrote:

C'est plus ou moins comme ceci que fonctionnent tous les langages
"objet" qui dérivent tous leurs types d'un type abstrait unique.



Je ne le crois pas. C'était le cas de Smalltalk, mais ce n'est
pas comme ça qu'on écrit en Java, ni en C++, ni en n'importe
quel langage OO moderne.



Il me semblait qu'n Java, tout dérivait de Objet ?

En tout cas, c'est le cas de .NET, ou tout dérive d'objet (sauf les
value type, mais c'est très limité. En gros, l'équivalent d'une simple
struct).

Ce qui implique hélàs un manque flagrant de typage statique. Je crois
que dans mes applications .NET (principalement de l'IHM, avec les
classes de base, plus deux biblitohèques externes, mais toutes sont
comparables en l'occurence), je me retrouve à faire du downcast depuis
Objet (ou un type un peu, mais pas beaucoup, plus spécifique) vers le
type que je manipule directement.

Je n'ai pas mesuré, mais à vue de nez, je crois ne pas exagérer en
disant que dans ce code, une ligne sur 5 contient un downcast.

--
Loïc


Avatar
James Kanze
Mathias Gaunard wrote:

[...]
Le fait que les fonctions virtuelles ne soient pas activées par défaut
montrent bien l'intention de faire de la programmation orientée objet
une fonctionnalité à part.


Même dans la programation orientée objet, la plupart des
fonctions ne doivent pas être virtuelles. Quand je fais du
Java, plus de la moitié des fonctions sont « final ». Dans la
mesure où le langage ne supporte pas la programmation par
contrat integrée, c'est prèsqu'essentiel si tu veux faire du
code robuste. (Au point que beaucoup d'experts dit qu'une
fonction publique ne doit jamais être virtuelle.)

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Sylvain
Loïc Joly wrote on 22/01/2007 20:57:

Je crois
que dans mes applications .NET (principalement de l'IHM, avec les
classes de base, plus deux biblitohèques externes, mais toutes sont
comparables en l'occurence), je me retrouve à faire du downcast depuis
Objet [..] vers le type que je manipule directement.


s'il s'agit "principalement d'IHM", ne manque-t-il pas une modélisation
polymorphe des classes en jeu qui éviterait tout (99%) downcast?

Sylvain.

3 4 5 6 7