La programmation en C++ moderne

Apprenez la programmation de zéro jusqu'à l'infini !

a marqué ce sujet comme résolu.

Merci pour ton retour. La bêta fur plus longue à être mise à jour parce que j’étais pas mal occupé et j’avais du mal à écrire : les idées ne s’enchainaient pas, les mots ne venaient pas et je n’étais pas satisfait de ce que j’écrivais. Maintenant ça va mieux, j’ai pu commencer le chapitre où je parle des itérateurs et des algorithmes. La partie sur les itérateurs n’est pas encore finie mais avance bien déjà.

Merci d’avance pour vos commentaires.

Je ne suis pas très fan de la division non plus. Une postcondition non respectée, c’est une erreur de programmation. Et là: deux approches: assertion (ou [[expect: ]] en C++20), ou TU. Les exceptions, c’est pour des cas indépendants de notre volonté. Je ne les mettrai pas au milieu de calculs (hors cas où il est moins cher de vérifier en cours de route comme des inversions de matrices), mais en amont: vérification des entrées externes au binaire en cours d’exécution.


Les itérateurs ne bouclent pas. Mais c’est un machin que l’on utilise pour itérer sur les éléments du collection dans des structures de contrôle de type boucle. On (nous!) boucle sur des éléments en déplaçant un curseur. Ce curseur peut être modélisé via un itérateur ou un index. Dans ce §, on aborde les itérateurs.

Effectivement, point important des itérateurs du standard C++: ils pointent sur des éléments là où les itérateurs du GoF, d’autres langages ou d’autres frameworks pointent entre deux éléments.

Grâce au C++11, on n’a pratiquement plus jamais besoin d’exprimer le type exact d’un itérateur. C’est ici l’endroit idéal pour introduire auto ou les for range-loop. Saute la partie qui permet d’avoir un itérateur qui ne référence rien, de toute façon, c’est une mauvaise pratique. Ne leur montre pas comment avoir des itérateurs dans le vide, ou alors pour mettre une note qui dise que c’est certainement une mauvaise idée.

end(), c’est la position au delà du dernier élément.

Après, travailler sur des plages semi-ouvertes (ou semi-fermées), c’est la façon type en C (où on itère entre 0 et N-1 en testant != N, ou < N . Ca marche très bien pour les itérateurs qui pointent sur des éléments (et non entre). Et surtout, c’est la façon la plus simple pour exprimer des plages vides.

Tous les conteneurs fournissent aussi l’opération inverse

Non. Il m’est facile d’en écrire un qui ne soit pas bi-directionnel. Beaucoup de BDs qui sont des surcouches simples à fichiers, ne sont que forward d’ailleurs. Mieux: std::forward_list<> :P

conteneurs à accès aléatoire

En VF, on dit "à accès direct".

Heureusement, la bibliothèque standard est bien fournie

Attention, cela ne veut pas dire que cela doit être utilisé. Parfois, c’est nécessaire, mais il y a probablement une meilleure façon de travailler.

Je me souviens d’une implémentation de bibliothèque type STL de Roguewave qui offrait un list::operator[](). Devine le genre de code que j’avais vu:

1
2
for (int i=0; i != liste.size() ; ++i)
   do_stuff(liste[i]);

Ou comment améliorer facilement les perfs d’un bout de code.... => quand on a des séquence chainées, il faut oublier les index et basculer à 100% sur des itérateurs -> une fonction de recherche doit renvoyer un itérateur et non un index, une fonction de retrait doit retirer un élément via un itérateur direct vers l’élément à retirer et non un numéro d’index, etc.

Bref, ces fonctions peuvent avoir un intérêt pour écrire des algorithmes génériques. Avant un tel chapitre dans ton cours, je n’en parlerai même pas.

Merci pour tes retours. Je confirme que moi non plus, je ne suis pas satisfait du chapitre sur la gestion des erreurs. En même temps je trouve que cette notion est importante, en même temps je trouve ça complexe à expliquer clairement. Je pense vraiment à simplement faire un chapitre sur les exceptions, sans plus détailler assert que ça.

Quand je parlais des conteneurs qui font l’opération inverse, je parlais des conteneurs standard uniquement, pas des conteneurs écrits soi-même.

Concernant ces opérations, effectivement, je réfléchissais à les mettre dans le chapitre sur les templates. Je vais retirer cette partie et puis aborder dans les algorithmes les façons de récupérer un itérateur sur un certain élément.

Merci pour tes retours. :)

Il n’est jamais manipulé autrement qu’avec des itérateurs – hors phase de remplissage, et hors parcours "indirect" avec for (auto&& e : fwdl). Après, c’est facile à corriger: au lieu de "tous", cela devient: "la plupart des itérateurs des collections standards sont (au moins) bidirectionnels".

Plus la possibilité de signaler au moment ad’hoc "Ah, au fait, les stream_iterator sont juste input ou output". "Ah, au fait, regardez, back_inserter() renvoie un output iterator. Mais d’ici là, tu auras eu l’occasion de présenter les algos avec les contraintes/attentes que l’on trouve dessus.

Salut à tous,

Du coup, j’ai bien intégré tes corrections et j’ai repris la partie sur la PpC dans le chapitre sur les erreurs. J’aimerai beaucoup tes retours dessus. Je l’ai simplifiée et je vais étaler sur plusieurs chapitres.

  • Je parlerais de l’approche tolérante / exigeante dans le chapitre sur le découpage des fonctions et j’y reviendrai avec les classes, private / public.
  • Je parlerais des tests unitaires dans une conclusion, pour aller plus loin, en précisant qu’ils servent à vérifier nos postconditions.
  • Je parlerais de l’utilisation des exceptions pour vérifier les postconditions dans un chapitre sur les fichiers, pour montrer un cas concret d’erreur externe. Ou bien l’allocation dynamique, même si je ne sais pas encore quand l’introduire.

J’ai bien précisé aussi qu’il s’agit d’un domaine complexe, sur lequel il n’y a pas de consensus universellement partagé, ni une unique et parfaite façon de faire.

Merci d’avance pour vos commentaires.

Re!

J’ai bien précisé aussi qu’il s’agit d’un domaine complexe, sur lequel il n’y a pas de consensus universellement partagé, ni une unique et parfaite façon de faire.

Pourtant, le conseil : "vérifiez tout ce qu’il est logiquement possible de vérifier avant le runtime du client par assertion et utilisez les exceptions pour le reste" marche bien. Reste à mon sens un seul addendum : "s’il existe des endroits ou malgré tous vos efforts, vous n’êtes pas sûrs que l’assertion est bien vérifiée (pas assez de tests par exemple), alors mettez un chien de garde à cet endroit". Chien de garde qui peut l’être au sens propre (un watchdog) ou un exception posée dans le code client pour dire "bon ben en fait c’est la merde".

(Et je parlerai même pas des techniques de preuves mathématiques sur le code :) ).

  • Je parlerais des tests unitaires dans une conclusion, pour aller plus loin, en précisant qu’ils servent à vérifier nos postconditions.

Ils servent à vérifier les postconditions ET les préconditions en fait. Parce que généralement la fonction testée appelle d’autres fonctions dont les préconditions d’appels seront par conséquent vérifiées avant l’exécution de la fonction.

  • Je parlerais de l’utilisation des exceptions pour vérifier les postconditions dans un chapitre sur les fichiers, pour montrer un cas concret d’erreur externe. Ou bien l’allocation dynamique, même si je ne sais pas encore quand l’introduire.

Tu ne vérifies généralement pas la postcondition. La postcondition, elle est assurée par la fonction appelée. Toi, ce que tu fais c’est simplement éjecter des sous-cas valides de la postcondition qui ne sont pas valides dans ton contexte à toi. Et si la fonction appelée vérifie certaines choses pendant son exécution et est susceptible de lancer une exception, ce n’est pas parce qu’elle a fait une erreur, c’est parce que quelque chose d’indépendant de la volonté du développeur ne permet as de faire le job. C’est pas un vérification de post-condition (et ça n’empêche généralement pas que l’on va avoir une sorte de postcondition à assurer).

Après, je trouve pour ma part dommage que les tests unitaires ne soient pas plus expliqués et soient expliqués si tard. On ne pousse pas le débutant à déterminer ce que sa fonction doit faire avant qu’il l’écrive.

  • Je parlerais de l’utilisation des exceptions pour vérifier les postconditions dans un chapitre sur les fichiers, pour montrer un cas concret d’erreur externe. Ou bien l’allocation dynamique, même si je ne sais pas encore quand l’introduire.

Tu ne vérifies généralement pas la postcondition. La postcondition, elle est assurée par la fonction appelée. Toi, ce que tu fais c’est simplement éjecter des sous-cas valides de la postcondition qui ne sont pas valides dans ton contexte à toi. Et si la fonction appelée vérifie certaines choses pendant son exécution et est susceptible de lancer une exception, ce n’est pas parce qu’elle a fait une erreur, c’est parce que quelque chose d’indépendant de la volonté du développeur ne permet as de faire le job. C’est pas un vérification de post-condition (et ça n’empêche généralement pas que l’on va avoir une sorte de postcondition à assurer).

Ksass`Peuk

Oui, je me suis mal exprimé (fatigue). Je pensais aux exceptions lancées quand quelque chose d’externe t’empêche de remplir tes postconditions. Il me semble avoir lu ça dans le bouquin du koala. :)

Après, je trouve pour ma part dommage que les tests unitaires ne soient pas plus expliqués et soient expliqués si tard. On ne pousse pas le débutant à déterminer ce que sa fonction doit faire avant qu’il l’écrive.

Ksass`Peuk

Je sais que les tests unitaires sont essentiels à tout bon programme, mais ils sont tout bonnement impossibles à expliquer à un débutant à ce stade. Pourquoi ? Pas parce qu’ils sont compliqués ou quoi que ce soit, non non, mais parce que il faut installer une bibliothèque externe. Avec C# c’est simple, c’est inclus « par défaut ». Avec C++, il faut installer Boost (ou autre, mais j’aime Boost). Ou alors on utilise des frameworks plus simples, comme celui-là. Mais il y a plusieurs choses à expliquer au préalable, comme les quelques commandes de préprocesseur.

Pour les TU et les pré-conditions, je suis assez circonspect – ou alors j’ai mal compris ce que tu as voulu dire Ksass`Peuk . Je ne sais plus où j’avais lu ça, mais cela m’avais assez convaincu: il ne rime à rien de tester le comportement d’une fonction sur une situation qui ne respecte pas ses préconditions. La fonction déclare, qu’il ne faut pas l’appeler ainsi, qu’elle a alors le droit de faire ce qu’elle veut, alors, ne l’appelons pas.

Un micro-onde n’est pas censé pouvoir fonctionner avec du métal dedans, ne le testons pas pour voir si par hasard il va bien mettre le feu à la cuisine comme il est écrit dans la doc.

Lorsque l’on a:

1
2
3
void f(){
  g();
}

Le test va vérifier que la pré-conditions de g est bien respectée lors de l’appel effectué par la fonction f. C’est bien une vérification de respect des pré-conditions. Et de fait, on va en faire.

Par ailleurs pour le coup des fonctions hors spécifications, ça dépend comment on voit les choses. S’il y a moyen que le point d’entrée soit accessible, je veux le vérifier pour ainsi dire quelque soit la situation sinon ça peut devenir un exploit.

Salut à tous,

Au programme de cette petite mise à jour, l’ajout de la section sur les algorithmes, ainsi que d’un passage avec const et std::cbegin/cend pour la section présentant les itérateurs.

Dans la suite, je vais présenter des algorithmes simples, plus ceux avec prédicat, puis écriture des lambdas.

Salut à tous,

Le chapitre sur les algorithmes s’étoffe au fur et à mesure, avec déjà un premier exercice. Si vous avez des idées d’ailleurs d’exercices, n’hésitez pas à me faire signe. :)

Autre nouvelle excitante : le tutoriel est actuellement en pré-validation. Taurre s’occupe de relire les premiers chapitres déjà complets et qui ne bougeront plus, pour voir si les propos sont clairs et pour corriger les fautes de français. Une fois le chapitre sur les algorithmes terminé, je l’enverrai en validation tel quel, afin que les lecteurs et ZdS puissent en profiter dès maintenant, car la période des vacances risque d’être creuse et de ne pas voir beaucoup le cours avancer. :)

Il restera, dans la première partie, la partie sur les templates, sur le découpage en fichiers, sur l’utilisation des namespaces, et sur la manipulation des entrées sorties (ouvrir des flux, itérateurs de flux, etc). Avec ça, la première partie sera terminée.

Merci d’avance pour vos commentaires.

La solution concrète à ne pas pouvoir utiliser +7 sur tout type de conteneur devrait être std::next(begin(phrase), 7).

une lambda est en total isolation

Manque un e à totale

.

Il existe aussi la possibilité de donner le type explicite de la lambda, qui est le type std::function

C’est faux, mais faut peser le pour et le contre pédagogique…

Le contenu de ce chapitre est très bien :) , mais à mon humble avis il est beaucoup trop long pour être absorbé d’une traite. Tu gagnerais à le couper en 2 voire 3 chapitres (le dernier spécifiquement sur les lambdas).

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte