La programmation en C++ moderne

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

a marqué ce sujet comme résolu.

Tu es motivé en ce moment :D

Je n’ai jamais pris le temps de lire ton cours et les codes d’exemple, du coup j’ai quelques questions.

  • tu utilises parfois \n, parfois std::endl. Il faudrait homogeneiser ? (Ou c’est volontaire ?)

  • dans ce code, tu utilises auto. Ca serait pas mieux d’utiliser decltype(compteur) ?

1
for (auto i { 0u }; i < compteur; ++i)
  • tu utilises des unsigned, mais il y a eu plusieurs discussions (a CppCon et sur les forums ici je crois) a propos de l’utilisation de signed vs unsigned. Et l’idée generale etait d’utiliser signed par defaut et reserver unsigned pour représenter les tailles des collections (héritage historique de la STL). Tu en penses quoi pour dans un cours ?

  • pas de déduction de type dans ce code ? (Alors que tu utilises la deduction avec {} dans la boucle for. A homogénéiser ?)

1
unsigned int repetitions { 3u };
+2 -0

Oui, j’ai une forte motivation en ce moment, ainsi que du temps disponibe donc j’en profite. :)

  • Pour les retours à la ligne c’est voulu, pour habituer le lecteur aux deux formes.
  • Je n’ai pas introduit decltype encore dans ce cours. Peut-être combiner les deux en decltype(auto) ? Je ne sais pas, à réfléchir.
  • J’utilise unsigned quand je me dis qu’il n’y a pas de sens d’autoriser le stockage de valeurs négatives, comme des notes ou des âges. Sinon je n’ai jamais réfléchi plus que ça.
  • Oui, un oublis ici.

Merci pour tes commentaires. N’hésite pas à m’en faire d’autres. :)

Tu es motivé en ce moment :D

Clairement, oui :)

Quand tu présentes le besoin derrière les fonctions, je me rends compte que le terme C++ ne décrit pas le besoin typique que tu présentes: fonction fait référence aux maths, c’est pour réaliser un même calcul sous des conditions, entrées, différentes: -> sin(0), sin(pi/4), … Le besoin de factoriser une séquence d’instructions en appelle plutôt à la notion de routine ou de procédure (que l’on émule en C++ avec des fonctions void qui ne retournent rien).

Tout ça pour dire: faut-il faire une parenthèse vocabulaire pour aider un pur débutant à assimiler le vocabulaire C?

En termes de vocabulaire, je sais que jeune ça ne rentrait pas, mais aujourd’hui, c’est une évidence qui m’aide à bien énoncer la différence entre f(foo); et f(int foo);. Dans le premier cas, foo est un paramètre réel, dans le second cas c’est un paramètre formel. En anglais, (dans la norme en tout cas), parameter et argument désignent l’un et l’autre respectivement (mais je ne sais plus dans quel sens). Cette différence de vocabulaire peut aider pour faire distinguer le cas où l’on appelle donc une fonction avec un nom de lvalue utilisée en paramètre réel qui est identique au nom du paramètre formel, et qu’il faut bien comprendre qu’il s’agit techniquement de deux variables différentes, tout ça.

EDIT: je ne m’étais pas relu. ^^’ Phrases corrigées.

f(foo); foo = argument

f(int foo); foo = paramètre

pour habituer le lecteur aux deux formes.

Quel est l’intérêt ? (Pour les débutants, j’ai fait le choix de proposer le minimum de syntaxe, pas de toutes les présenter. Tout dépend de ton public cible.)

Sinon je n’ai jamais réfléchi plus que ça.

Je ne sais pas si tu as vu les discussions a propos de ca a la CppCon ?

+0 -0

Je vais me relire pour voir les points où j’ai utilisé une expression à la place de l’autre pour corriger. C’est assez mineur, moi-même en vrai j’utilise les deux indépendamment dans mes discussions avec d’autres développeurs, mais soyons rigoureux pour les débutants.

pour habituer le lecteur aux deux formes.

Quel est l’intérêt ? (Pour les débutants, j’ai fait le choix de proposer le minimum de syntaxe, pas de toutes les présenter. Tout dépend de ton public cible.)

gbdivers

Dans le tout premier code, je présente le std::endl, mais dans le chapitre sur les littérales, je parle des caractères spéciaux dont '\n', donc je me suis dit qu’utiliser les deux allait aider le lecteur à retenir ce caractère spécial qu’on voit souvent.

Mais je suis d’accord, mieux vaut présenter le minimum de syntaxes, c’est ce que je m’efforce de faire également.

J’ai bien avancé dans la partie sur les références. Je réfléchis maintenant si j’introduis decltype tout de suite ou plus tard dans le cours. La section d’après sera consacrée aux exercices, pour digérer toutes les nouveautés et bien fixer tout ça en mémoire. Ensuite, je compte parler de la gestion des erreurs et de la réflexions sur les conditions (pré et post), avec notamment assert. J’aimerai aussi parler du polymorphisme ad-hoc, de inline et des trailing functions, mais est-ce que ce n’est pas trop pour le lecteur en un seul chapitre ? Une chose est sûre, c’est que je ne vais pas aborder les lambdas et les templates dans ce chapitre.

Merci d’avance pour vos commentaires.

Salut à tous,

Voilà le début de la section sur les exceptions. Bien entendu, cela n’est pas satisfaisant pour un expert C++ parce qu’il manque plusieurs choses, mais n’oublions pas que nous avons affaire à des débutants qui n’ont jamais entendu parler de C++ ni d’exceptions jusque là.

Merci d’avance pour vos commentaires.

a- AMA, il faudrait découper le § sur les fonctions. Il est long. Typiquement, parle des exceptions ailleurs. La partie sur la déclaration avant utilisation pourrait être bien isolée dans un "comment comment organiser le code"

b- Je ne parlerai pas de decltype() non plus. En fait, dans la formation "migration C++11" que je donne au boulot, j’ai des slides sur le sujet, mais je n’en parle jamais.

c-

Dans le cas où l’on agrandit le tableau, on peut choisir soit d’assigner

"assigner" est pour moi un angliscisme dans notre contexte, je préfère affecter, voire "attribuer" dans ce contexte précis.

d-

Le côté négatif, c’est qu’on doit forcément copier le paramètre, même si on ne le modifie pas. Dans le cas d’un entier ou d’une caractère, pas de problème

Plus que ’pas de problème’, c’est même beaucoup plus efficace de la sorte, c’est "idéal".

e-

Une référence sur un double ne peut pas se voir affecter une variable de type int

Mieux qu’"affecter" -> "associée à" ou autres équivalents. Car on peut affecter un entier via une référence sur un double.

f-

Analysez le code suivant, sans le lancer, et dites moi ce que vous pensez qu’il va faire. Si vous me dîtes qu’il va simplement afficher deux messages

-> "différents" les messages

g-

Parce qu’ils sont très petits et le coût de la création d’une référence est souvent plus élevé que recopier

Je dirai que c’est surtout le coût de l’exploitation qui est plus important. Sans parler des aliasing possibles qui gênent les optimisations.

h-

Je l’ai écris plus haut mais je le remets bien lisiblement ici : dès qu’un appel à une fonction,

quand on m’avait initié aux exceptions il y a bien longtemps, mon prof avait utilisé le terme "dérouter" qui explique bien ce qu’il se passe.

Je réponds brièvement parce que je suis sur téléphone et que c’est plus galère de faire du texte long, mais concernant les reformulations et les anglicismes, je vais faire ça.

Pour les exceptions, oui, peut-être qu’il vaut mieux faire un chapitre séparé, qui viendra juste après celui sur les fonctions. Par contre, pour decltype, je pense que c’est une bonne idée de l’introduire à ce moment. Sinon quand ?

Merci pour tes précieux retours. :)

Salut à tous,

Comme suggéré, j’ai déplacé la partie sur les erreurs dans un chapitre à part, bien évidemment pas encore fini. J’ai ajouté une partie sur le polymorphisme ad-hoc, mais pas encore tout à fait fini. Je réfléchis du coup à intégrer maintenant ou non les templates et les lambdas, mais j’ai peur que ça rend le cours trop lourd.

Merci d’avance pour vos commentaires.

Salut à tous,

Finalement, le chapitre est assez complexe comme ça. J’ai donc ajouté la conclusion et je vais le laisser tel qu’il est. Maintenant, le programme est le suivant.

  • Gestion des erreurs avec introduction des exceptions et réflexions sur nos fonctions.
  • Les itérateurs, avec cas concrets des algorithmes et introduction des lambdas comme solution au problème « Est-ce que je dois créer une vraie fonction juste pour mon algorithme ? »
  • Les templates pour montrer comment faire du code générique, avec notamment expliquer plus précisément que c’est ce qui est utilisé par les algorithmes et les conteneurs et c’est pour ça qu’on peut les utiliser avec n’importe quel type. Je compte aussi introduire à ce moment tout ce qui peut se vérifier à la compilation, comme static_assert, les traits et aussi constexpr.
  • Découpage en fichiers, parce que notre code commence à prendre du volume, avec notamment quelques explications sur le préprocesseur mais qui seront brèves parce qu’on aime pas trop cet héritage du C. :)

Après ça, je pense que la première partie sera achevée et prête à être validée. Ensuite, c’est beaucoup plus flou et je n’y ai pas encore réfléchi. Je ne sais pas dans quel ordre je le ferai.

  • On aura une partie sur l’OO avec sémantique d’entité / de valeur, etc.
  • On aura aussi une partie C++ avancée, avec tout ce que je n’ai pas introduit dans la première partie, en mode « Vous pouvez programmer maintenant, mais voilà encore plein de choses spécifiques à C++ qui feront de vous de meilleurs développeurs C++ ». Notions en vrac qui me viennent en tête mais que je n’aborde pas dans la première partie, on parlera de move-semantic, des pointeurs, l’héritage du C (tableaux statiques, const char *, etc).

Merci d’avance pour vos commentaires.

Salut à tous,

Alors j’ai avancé sur la réflexion sur les fonctions, avec notamment savoir dans quel cas on penchera plutôt pour les assertions et dans quel cas pour les exceptions. Je sais que ce chapitre est dur et consistant, mais personne n’a dit qu’apprendre C++ correctement serait facile. :)

J’attends toujours avec impatience vos retours.

Merci d’avance pour vos commentaires.

Salut à tous,

J’ai fini le chapitre sur la gestion des erreurs, qui aura été le plus dur à écrire de tous. Il est difficile, j’en suis bien conscient.

Maintenant, je compte écrire le chapitre sur les itérateurs, avec notamment les algorithmes et les lambdas, puis le chapitre sur les templates et enfin celui sur le découpage de code en fichiers et namespaces. À voir si je vais un chapitre sur les fluxs aussi ou pas.

Merci d’avance pour vos commentaires.

Salut,

Dans ton premier exemple avec une exception, tu précises :

Pour se faire, il faut d’abord inclure le fichier d’en-tête <exception>.

Mais dans le code qui suit immédiatement, tu inclues <stdexcept> à la place, pour avoir accès à std::runtime_error. Il faudrait donc rectifier ta première phrase :-)

Une faute de frappe s’est également glissée là où tu parles de std::abord qui est bien sûr std::abort.

Au début tu parles de « lancer une exception », puis sans prévenir, au détour d’un commentaire de code d’exemple, tu passes sur l’expression « Dès qu’une exception est levée ». C’est possible de conserver les deux expressions, mais dans ce cas il faut les introduire toutes les deux ; ou alors n’en conserver qu’une et s’y tenir sur tout le cours.

Pour le reste, je trouve la rédaction fort bonne et agréable à lire :)

Je suis un peu circonspect sur l’exemple suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
double division(double numerateur, double denominateur)
{
    assert(denominateur != 0);
    double resultat { numerateur / denominateur };

    // Si notre postcondition est fausse à cause du processeur, par exemple.
    if (resultat * denominateur != numerateur)
    {
        throw std::runtime_error { "Erreur de processeur : résultat invalide." };
    }

    return resultat;
}

Outre que l’exemple me convainc peu, il est difficilement compréhensible pour quelqu’un qui ne serait pas familier des questions de calculs flottants dans les FPU. Cela pourrait entraîner, malheureusement mais inévitablement, une paranoïa des débutant·es vis-à-vis des calculs de leur CPU. Je n’ai pas d’exemple qui me vienne en tête immédiatement, mais je pense qu’il faudrait trouver un exemple avec un contexte interne et non externe.

Tout cela étant relevé, est-ce que tu as l’intention de reparler quelque part des exceptions ? Je pense à un certain nombre de notions pas encore présentées :

Pour les plus basiques (qui à mon sens méritent bien une place dans un cours pour débutant·e) :

  • throw; pour relancer une exception
  • la possibilité de throw autre chose qu’une std::exception
  • la possibilité de créer ses propres classes d’exception
  • catch(...) pour attraper réellement toutes les exceptions (contrairement à ce qui est avancé dans un but je présume didactique std::exception [...] nous permet de rattraper n’importe quel exception lancée)

Pour les plus avancées :

  • system_error (avec error_code, error_condition, etc.) qui permet de faire un pont entre les différentes API et les systèmes de codes d’erreur qui sont souvent vus à tort comme opposés aux systèmes d’exceptions
  • throw_with_nested parce que c’est plutôt courant dans d’autres langages que le C++, et fort utile quand on emboîte plusieurs couches d’API ou de protocoles
+0 -0

Salut germinolegrand et merci pour ton retour. :)

Concernant les fautes d’inattentions, je les ai corrigées, merci de les avoir soulevées.

Concernant l’exemple avec la division, je n’en suis pas satisfait non plus et je pense qu’il devrait être supprimé. Mais comme je n’ai pas abordé la gestion des fichiers, je n’ai aucune idée d’exemple où les postconditions peuvent ne pas être respectées à cause d’une erreur externe. Peut-être un std::vector trop gros ?

Pour répondre à ta question, oui, je reparlerais des exceptions plus tard dans le cours, notamment pour illustrer le polymorphisme ainsi que la sémantique d’entité. C’est pour cette raison que je ne précise pas comment créer les siennes ni comment marche réellement std::exception.

Pour throw int ou autre, ainsi que `catch (...), ce sont des pratiques déconseillées qu’il ne vaut mieux utiliser que quand on sait bien ce qu’on fait. Je n’ai pas envie de rentrer ça dans la tête d’un débutant pour le voir écrire des catch (...) partout. D’où également mon mensonge sur catch (std::exception) qui rattrape tout.

Quand à relancer une exception, je vais essayer d’en parler oui. Je l’avais oublié.

Encore merci. :)

Le std::vector trop gros, c’est parfait : imagine y’a 20 places de cinéma réservables, quand tu essayes d’en rajouter une 21e, paf ! exception :-) .

Bon pour ce qui est de la mauvaise pratique, j’avoue ne jamais balancer de std::exception sauf pour une API de bibliothèque (et très souvent ça va être une system_error avec error_code, error_category et tout le tralala). En interne je préfère une classe simple d’un type quelconque et unique et la plupart du temps sans aucun attribut ni fonction membre parce que c’est pas fait pour être utilisé par autre chose que le code appelant, et que y’a pas de raison de se prendre le chou à écrire du code superflu pour une exception (autrement on se retrouve à passer plus de temps à coder les exceptions que le cas principal). Bien évidemment jamais de throw int ni de catch(...) sans rethrow, ça va de soi. Mais j’entends que ça ne soit pas ce qu’on veuille enseigner.

Ah, dans les choses intéressantes à mentionner au sujet des exceptions, je viens de me souvenir des function-try-block, qui sont parfois le seul outil utilisable (mais j’admets c’est très rare).

Pour le catch(...) il m’est plus souvent utile qu’un catch(std::exception&), dont il faut connaître les limites, surtout si on retrow derrière. Ici un petit code tiré de cet article arrangé à ma sauce pour illustrer : https://wandbox.org/permlink/64ruiK6LSJvT8Qnn

Quand on se contrefiche de l’exception elle-même, et qu’on veut seulement nettoyer avant de rendre la main, un catch(...) est beaucoup plus approprié. Et si on est vraiment curieux de l’exception, on peut toujours faire appel à std::current_exception().

EDIT: la partie maintenant cachée est fausse, parce qu’il est beaucoup trop tard et que je n’ai pas relu mon code, ça m’apprendra à copier-coller ! En effet, si on met des throw; et non des throw e; partout, l’original est bien conservé et non copié. My bad. Reste qu’avec le catch(...) on ne risque pas de se tromper comme cela vient de m’arriver ^^

+0 -0

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.

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