La programmation en C++ moderne

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

a marqué ce sujet comme résolu.

Reprise du dernier message de la page précédente

@Dedeun

Pour le 1), c’est une question assez complexe. Cela va dépendre du public visé, c’est assez difficile de trouver le bon ton. Mais :

  • OC change de nom, ce n’est pas pour rien. Et ils mettent pas mal en avant le professionnalisme de leurs formations (ingénieure en pédagogie).
  • il ne faut pas confondre attractivité et jouer au bon copain.

Prendre un ton très léger, ça va plaire a certaines personnes et pas d’autres. Le gros probleme de OC, a mon sens, est que cela intéresse beaucoup (trop ?) des personnes qui recherchent un apprentissage superficiel et rapide. Ce qui n’est pas le but de ce tutoriel, a priori. Prendre un ton qui ne correspond pas a l’objectif serait une erreur. (Mais c’est aux auteurs de voir le ton qu’ils veulent prendre)

@mougnolribama

Cet impact la je l’avais plus ou moins compris. Je parlais de comprendre les avantages ou les inconvenients de certains usages. Parfois pour moi c’est la meme chose et les commentaires sur la bonne facon d’ecrire un programme sont souvent un peu obscurs.

Certaines recommandations viennent de l’expérience de centaines d’experts qui ont discutés pendant des années, sur des milliers de projets et des millions de lignes de code. Pour cela qu’il est difficile de tout justifier. Meme pour nous, qui sommes devs expérimentés, on ne connait pas forcement en détail tous les arguments (ou on n’est pas tous d’accord).

Donc il faut accepter (pour nous aussi) de respecter les "bonnes pratiques" de programmation, meme si on ne connait pas les justifications. En C++, une référence est les C++ Core Guidelines (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)

C’est plus un probleme de rythme. J’ai plus souvent l’impression que je fais 10 fois plus d’effort que certains pour y ariver que je ne vais jamais y arriver. Mais tes conseils sur le sujets, je me les repassent en boucle histoire que ca rentre et que je devienne le genre d’informaticien que j’ai envie d’etre ( tres tres bon).

Pareil que la confiance, il ne faut pas trop se formaliser la dessus.

Il n’y a pas qu’un seul type de "bon" développeur. Tu mets plus de temps, mais est-ce que tu ne vas pas avoir une meilleure compréhension qu’eux ? Est-ce que tu ne vas pas mieux retenir les informations sur le long terme ? Est-ce que tu ne vas pas être plus imaginatif pour trouver des solutions ? Est-ce que le code que tu produis ne va pas être plus stable et maintenable sur le long terme ?

C’est une tendance qu’on voit de plus en plus de les façons d’apprendre et de pratiquer d’aller au plus rapide, de produire du code le plus vite possible, comme si le nombre de lignes de code écrit a la minute avait une importance. C’est une chose qu’on voit pas mal dans certaines formations et dans les sites d’exos en ligne, mais, pour moi, cela destine plus a former des "pisseurs de code" que des devs. (Pour être concret, au boulot, la plus part des jours, je commit une dizaine ou une centaine de ligne de code sur la journée. Donc très peu. Je passe plus de temps a réfléchir a mon code qu’a l’écrire.)

Une autre question. C’est a propos de la documentation. j’aime souvent lire les liens en "fr". Je me demandais si c’etait equivalent en "en"?

Si tu parles de cppreference.com, il ne faut pas utiliser la version fr. cpprefrence.com très très bien maintenu a jour sur la version en, donc c’est une bonne référence. Mais les versions non en ont été généré automatiquement lors de la création du site et devaient être maintenues par les communautés. Mais ce n’est pas le cas. Donc c’est pas a jour. Si tu veux vraiment lire en français, il est préférable de prendre la version en et faire une traduction automatique de la version a jour dans google translate.

Édité par gbdivers

Pour poser des questions ou simplement discuter informatique, vous pouvez rejoindre le discord NaN.

+2 -0

Je me demandait comment je fais pour savoir si quelque chose n’a pas deja ete dit a propos d’une faute par exemple d’orthographe sur le tutoriel. Et puis voila il y a ceci

Pour vous faciliter la vie, découper l’exercice en deux. Faites d’abord la fonction permettant de supprimer tous les espaces à gauche, puis pour ceux à droite. Bon courage. ;)

dans la partie string_trim. Les fonctions c’est dans "On passe la deuxieme". C’est un peu tot pour cette formulation non?

Édité par mougnolribama

in medio stat virtus

+0 -0

Certaines recommandations viennent de l’expérience de centaines d’experts qui ont discutés pendant des années, sur des milliers de projets et des millions de lignes de code. Pour cela qu’il est difficile de tout justifier. Meme pour nous, qui sommes devs expérimentés, on ne connait pas forcement en détail tous les arguments (ou on n’est pas tous d’accord).

Donc il faut accepter (pour nous aussi) de respecter les "bonnes pratiques" de programmation, meme si on ne connait pas les justifications. En C++, une référence est les C++ Core Guidelines (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)

Merci pour les coresGuidlines. En plus, c’est pas l’essentiel d’être beau, mais je trouve ça très bien présentée.

Pareil que la confiance, il ne faut pas trop se formaliser la dessus.

Encore des paroles de sage gardien. C’est vrai qu’il faut se donner du mal (du temps?) pour avoir un résultat réellement satisfaisant (devenir vraiment bon).

Si tu parles de cppreference.com, il ne faut pas utiliser la version fr. cpprefrence.com très très bien maintenu a jour sur la version en, donc c’est une bonne référence. Mais les versions non en ont été généré automatiquement lors de la création du site et devaient être maintenues par les communautés. Mais ce n’est pas le cas. Donc c’est pas a jour. Si tu veux vraiment lire en français, il est préférable de prendre la version en et faire une traduction automatique de la version a jour dans google translate.

C’est en effet de cppreference dont je parlais. Je trouve cette doc plus explicit que devdocs. Merci pour l’information concernant l’obsolescence des liens "fr". J’essaye de lire autant que possible en anglais et quand ça commence à piquer les yeux je préfère traduire automatiquement.

in medio stat virtus

+0 -0

Salut à tous,

Au programme de la bêta, le début du chapitre sur la sémantique de mouvement. N’hésitez pas à faire des retours dessus, comme toujours, nous attendons avec impatience vos commentaires et idées d’amélioration. :)

J’en profite pour annoncer qu’une fois ce chapitre terminé, ainsi que le T.P qui suit, nous demanderons la validation. Cela fera déjà un morceau de POO à étudier. Tant pis si l’étude de cas n’est pas prête, elle viendra plus tard.

Enfin, au passage, merci à toi @mougnolribama pour tes retours ! On n’imagine parfois pas, mais le meilleurs moyen de savoir si un cours est bien écrit reste de le faire lire à des débutants. Merci pour tes remarques. N’hésite pas à signaler si des passages ne sont pas clairs, trop long, ou si une notion est utilisée sans avoir été introduite au préalable.


Bonjour les agrumes !

La bêta a été mise à jour et décante sa pulpe à l’adresse suivante :

Merci d’avance pour vos commentaires.

Édité par informaticienzero

J’en suis à la seconde page : J’aurais voulu vous proposé une amélioration, mais, à la place, j’ai une question ! :euh:

Dans l’exercice du chapitre : https://zestedesavoir.com/contenus/beta/822/la-programmation-en-c-moderne/la-programmation-orientee-objet/classes-a-semantique-de-valeur/#soyons-intelligents-r%C3%A9utilisons Je suis un peu surpris du prototype de la fonction operator+, en effet, le premier argument n’est pas passés ni en référence, ni en const.

Fraction operator+(Fraction a, Fraction const & b)

Vu le codage, de la fonction, je pensais que si la variable passée pour l’argument « a » était un « const Fraction », ça ne marcherais pas ! Je pensai que le compilateur me sortirai au moins un warning ! … Et, c’est en fait, votre exemple (f1 est un const, dans votre exemple). Et pas de warning, pas d’erreur (j’ai vérifié). Je ne comprends pas pourquoi.

Pour contre-vérification, j’ai changé le prototype :

Fraction operator+(Fraction & a, Fraction const & b)

Et là j’ai une erreur à la compilation (« Enfin quelque chose de normal! »)

error: binding reference of type ‘Fraction&’ to ‘const Fraction’ discards qualifiers

Donc, le compilateur ne tient pas compte des restriction de constance, quand on passe un argument par valeur ?

Note: J’ai relu les chapitres https://zestedesavoir.com/contenus/beta/822/la-programmation-en-c-moderne/decoupons-tout-ca/decoupons-du-code-les-fonctions/#les-composants-dune-fonction, et les suivants. Quand on passe un paramètre par valeur, il n’est pas précisé les tests d’intégrités que fait le compilateur.

Note: Un petit détail, chapitre : https://zestedesavoir.com/contenus/beta/822/la-programmation-en-c-moderne/la-programmation-orientee-objet/classes-a-semantique-de-valeur/#3-le-retour-des-operateurs ce chapitre commence par : « Dans le chapitre 6 de la deuxième partie, … », si vous avez le temps, ce serait cool de mettre un lien hypertexte.

J’espère que ça peut servir à quelque chose.

Bien cordialement.

+0 -0

Hello

Je commente en vitesse

Ce que je lis

Le concept de mouvement, ou de déplacement, est apparu avec C++11

Le support propre par le langage est apparu avec le C++11. Mais le besoin était déjà connu. On avait l’ancêtre de l’élision de copie avec RVO/RNVO (/NRVO?), et on avait auto_ptr, une bidouille pas possible.

Et on bidouillait avec swap, ou le COW. Mais il manquait quelque chose de plus simple.

Exemple avec Fraction

Euh… Avec les fractions, le déplacement n’a aucun intérêt. Tout l’intérêt de déplacer, c’est sur les choses qui sont chères à acquérir: mémoire, ou chère à dupliquer mais qui sont manipulées via des indirections (histoire de s’échanger des malettes (manipulation de la poignée) plutot que des contenus de malettes (manipulation du contenu)). Voire pour lesquelles la duplication n’a aucun sens, mais que l’on aimerait bien voir se "déplacer": sockets, fichiers, locks…

Il vaudrait mieux soit attendre de voir un peu la manipulation de mémoire. Soit montrer comment on écrit une classe matrice, avec des vecteurs d’abord, à l’ancienne ensuite (new/delete). Puis avec des unique_ptr

return a += b;

Cela pourrit les possibilités de retour par élision.

Il faut d’abord incrémenter, puis renvoyer. en deux instructions. Parce que a+=b est une référence, et ça fout la merde.

: numerateur(std::move(autre_fraction.numerateur))

La bonne façon de faire sur les scalaires et autre données dont la construction par déplacement ne vole rien, c’est : numerateur(std::exchange(autre_fraction.numerateur, 0)).

Rule 0/5

Dans la variante du "tout ou rien", soit on n’explicite rien, soit on explicite tout (default, delete, ou définition avec corps).

ce que je ne lis pas

Rule tout/rien / 0/5

Pour savoir si on doit définir les opérations de déplacement, il faut aussi prendre en compte les invariants.

Avec un classe chaine telle que

struct String {
    ...
private:
    std::unique_ptr<char[]> m_buffer;
    std::ssize_t            m_length;
};

Les opérations générées par défaut vont désynchroniser le buffer et la longueur parce que le move(scalaire) ne fait rien au scalaire de départ.

Ce que font move et les && en vrai

Ca ne fait rien. Ca ne fait que permettre de matcher, ou pas, des expressions avec des fonctions. Et en plus, il y a des priorités.

Le déplacement véritable n’est effectif que sur construction ou affectation.

Je fais jouer mes élève avec ce bout de code pour qu’ils comprennent les implications de &&, et les effets de move.

#include <string>
#include <iostream>

std::string source() { return "src"; }

void f(std::string const& s) { std::cout << "f(const&)\n"; }
void f(std::string &      s) { std::cout << "f(&)\n";      }
void f(std::string &&     s) { std::cout << "f(&&)\n";     }
void f(std::string        s) { std::cout << "f()\n";       }

#define call(x)  std::cout << #x << "\t-> "; f(x);

int main ()
{
    std::string varloc = "vl";
    call(varloc);

    std::string const constvarloc = "cvl";
    call(constvarloc);

    call(source());
}
+2 -0

Je suis un peu surpris du prototype de la fonction operator+, en effet, le premier argument n’est pas passés ni en référence, ni en const.

Dedeun

Tu ne veut affecter aucune des deux opérandes dans l’opération "+", pourtant le résultat que tu renvoie est différent, ça signifie qu’il va forcément te falloir une instance locale à la fonction pour faire les opérations dessus avant de le renvoyer. Il a choisi de la mettre dans les arguments pour qu’elle soit directement initialisée avec les valeurs d’une des opérandes puisqu’elles vont servir au sein de l’opération.

jadis @leroivi - all in all we’re just another brick in the wall

+0 -0

Merci Romantik pour ta réponse.

Ma surprise est sur le fait que :

  • Le compilateur ne génère pas d’erreur/warning au sujet de la constante passée en argument (contrairement à ce que je pensais)
  • Je n’ai pas trouvé de réponse dans le chapitre précédent au sujet du contrôle des arguments passés par valeurs
  • Par erreur je pensais que operator+ avait pour argument deux ref constantes (Erreur ! :( )

J’aurai écrit:

Fraction operator+(Fraction const & a, Fraction const & b)
{
    Fraction tmp {a};
    tmp += b;
    return tmp;
}

Bien sur, une class avec 2 int, ou une reference/pointeur, en longueur dans la pile, c’est sûrement "kif-kif bouricot", donc passer une valeur ou une ref à l’opérator+ ça n’a pas d’importance. Mais si la class Fraction "grossie" ("Avec l’Age on prend du poid!"), ça pourrait devenir un problème.

Enfin, ce n’est les remarque que d’un pôve débutant, pas forcément pertinentes.

Bien cordialement.

+0 -0
  • Le compilateur ne génère pas d’erreur/warning au sujet de la constante passée en argument (contrairement à ce que je pensais)
  • Je n’ai pas trouvé de réponse dans le chapitre précédent au sujet du contrôle des arguments passés par valeurs
Dedeun

Au passage par valeur, tu utilises le constructeur par copie qui a la signature Fraction(Fraction const & autre_fraction);
Ainsi, pour répondre à ces 2 points, tu vois que l’argument peut être une instance constante c’est pour ça que ça ne le dérange pas qu’on lui donne une constante à l’appel de la fonction, et les conditions pour pouvoir faire un passage par valeur c’est simplement que ce constructeur existe (que la classe soit copiable)

Bien sur, une class avec 2 int, ou une reference/pointeur, en longueur dans la pile, c’est sûrement "kif-kif bouricot", donc passer une valeur ou une ref à l’opérator+ ça n’a pas d’importance. Mais si la class Fraction "grossie" ("Avec l’Age on prend du poid!"), ça pourrait devenir un problème.

Dedeun

La fonction que tu as écrite est correcte, pas de soucis, cependant, pour être clair :
Tu as en pile la référence a plus l’objet local tmp, alors que dans la solution du cours, il n’y a plus que l’objet local a
En mettant sur 2 instructions tmp += b; return tmp; plutôt que dans une seule return a+=b; tu profites d’une optimisation du compilo qui évite une copie (cf. remarque de lmghs)

Enfin, ce n’est les remarque que d’un pôve débutant, pas forcément pertinentes.

Dedeun

Toute remarque est bonne à prendre, hésite pas

(EDIT : par objet temporaire je voulais dire objet local à la fonction)

Édité par romantik

jadis @leroivi - all in all we’re just another brick in the wall

+1 -0

La POO

Principes

Avantages

J’ai du mal avec une partie des avantages présentés pour la POO. Je ne vois pas en quoi l’objet apporte un code plus modulaire, ou mieux abstrait. L’exemple présenté pour l’évolutivité est tout aussi vrai avec une API bien designée dans un monde procédural (comme en C), et pareil pour l’expressivité. Et c’est pareil pour le fonctionnel avec la notion d’API exprimée à travers les modules.

Pour moi tout ce qui est décrit dans la catégorie "avantages" c’est les bienfaits d’une API et de l’encapsulation. Pas de l’OO. L’OO n’a pas le monopole de la pensée en termes de services.

Le vrai changement en OO c’est d’avoir une mécanique native avec un cadre à peu près cohérent pour changer dynamiquement le comportement des objets. Alors oui, à ce stade du tutoriel, on ne peut pas encore introduire tout ça. Ça viendra en son temps avec la notion de polymorphisme.

Je n’ai pas lu la partie ZestyCreature mais je pense qu’elle est tout à fait le lieu pour introduire la notion d’API à travers un exemple. Le début de la partie OO pourrait formaliser un peu l’exemple en expliquant ce qu’est une API et la nécessité que c’est en programmation, quel que soit le langage (et donc lister les avantages présentés ici). A partir de là, on peut embrayer sur le fait qu’une première chose intéressante avec l’objet dans un langage à la C++, c’est de grouper ensemble les fonctionnalités et les données qui leur sont associées. Puis : voir dernier point de ce commentaire.

Exemple

Ici on oppose "impératif" et "objet". Or, en C++, l’objet est impératif. L’impératif s’oppose plutôt au déclaratif. Et l’OO de C++ par exemple n’est pas déclaratif.

En C++, ça donne quoi

Vocab

On a "objet" presque présenté comme synonyme de "donnée" ici. Alors que c’est plutôt une fournisseur de service, comme dit plus haut. (W. Cook parle de "comportement" pour les objets).

Quelle méthode doit on …

Son implémentation se fait quand à elle en dehors de celle-ci.

Peut être dit "peut se faire en dedans, ou préférablement en dehors comme dans l’exemple suivant".

Encapsulation

Un léger problème

Rappelons que cette opération est mathématiquement impossible.

Non définie sur les entiers

Une classe de grande valeur

Affectation par copie

Quand présente-t-on le copy and swap ? Il semble important.

Classes à sémantique d’entité

Des objets uniques !

Une entité est un objet tel qu’on le conçoit dans la vie réelle

Cette définition présente un gros risque. Le calque vie réelle <—> programme fait beaucoup de mal à une conception, en particulier chez les débutants.

Et là on revient au point que j’abordais plutôt. Ici, on a appris ce qu’est un objet en C++, ce qu’est une valeur, une entité, et on attaque déjà les questions d’héritages sans même avoir expérimenté comment on crée ces objets, les questions de conteneurs, etc.

A mon sens, il faudrait embrayer sur un exemple un peu conséquent de comment on utiliser une entité pour rendre un service et puis manipuler un peu la notion d’entité. Et introduire d’ors et déjà la notion de handle à travers un exemple con comme agrandir un vector d’objet non-déplaçables par exemple. Ça peut faire un exemple spécifique.

A partir de là, introduire un petit exemple où d’un coup on a besoin de faire varier une fonction mais que la seule solution qu’on a sous la main c’est une conditionnelle.

Bam, on introduit la notion d’héritage, la notion de polymorphisme dynamique, en quoi l’objet fournit son propre cadre pour cela (et là on présente un vrai avantage de l’objet - même si ici c’est un cadre particulier qui est l’orienté classe), on résout notre problème et on introduit les collections hétérogènes et le LSP comme contrainte logique de ces collections (et par extension de tout endroit où l’on attend du générique). Sur ça, on peut introduire les interface et l’ISP.

Après ça, le Javaquarium peut être l’occasion de mettre en pratique l’ISP puis d’introduire l’OCP et le DIP (et vous pouvez même montrer une limite de l’applicabilité de l’OCP en class oriented avec le visiteur dont on a besoin dans l’exo).

EDIT: d’ailleurs à bien y réfléchir, on peut rebondir sur ce que @lmghs à propos du mouvement. Peut être que du coup, le plus pertinent est de déplacer la section sur mouvement (pun intended) après la section entité, en même temps que les histoires de handlers. Ca permettra d’alléger un peu le paquet de trucs introduit dans la partie "valeur".

Édité par Ksass`Peuk

  • Par erreur je pensais que operator+ avait pour argument deux ref constantes (Erreur ! :( )
Dedeun

Ton code est valide aussi. Pour un débutant, c’est pas critique d’utiliser une valeur ou une ref constante. Plus précisement, la justification sort des explications qu’on peut donner aux débutants. Et si tu prends un livre C++ pré C++11, tu trouveras probablement la syntaxe avec la ref constante.

Je ne me souviens plus si le tuto en parle, mais pour la surcharge d’opérateur, pas trop besoin de réfléchir. Il existe des écritures idiomatiques, on copie-colle et c’est tout. Cf https://en.cppreference.com/w/cpp/language/operators

Il faudrait peut etre ajouter une note pour expliquer qu’il peut exister plusieurs syntaxes, mais que celle donnée dans le cours est la forme idiomatique.

Pour poser des questions ou simplement discuter informatique, vous pouvez rejoindre le discord NaN.

+1 -0

Des flux dans tous les sens

Très beau chapitre. Et quand je commençais à trouver ça, mais vraiment plus que passionnant, ça s’arrête. Sur Même les chaînes y passent !   il n’y a pas vraiment d’exercice. Et je sens que ca ne rentre pas trop. Surtout la:

std::string resultat { flux_chaine.str() };

Peut être faudrait-il juste que je les considère comme des flux dont je peux extraire des chaines? Ou des flux basés sur des chaines? Je lis le résumé:

Il existe des flux pour communiquer avec des fichiers (std::ifstream et std::ofstream), avec des chaînes de caractères (std::istringstream et std::ostringstream), avec l’entrée et la sortie standards (std::cin et std::cout).

sauf que les chaines de caractère réside en mémoire centrale. Ca peut paraitre bizarre ce que je vais dire, mais c’est ma façon de simplifier les choses et de comprendre l’utilité des flux. output on extrait quelque chose de la mémoire et input on y range quelque chose. Apres le probleme c’est pas de trop de discuter de la théorie, mais c’est justement qu’après avoir lu Même les chaînes y passent ! je ne vois pas trop l’intérêt, ou plutôt je n’arrive pas a imaginer le(s) cas général(aux) d’utilisation des ifstream. Parfois seul les exercices du tutoriel me pousse a relire ou a reconsidéré ma lecture de façon a ce que je comprenne a peu près l’intérêt et les usages qui entourent une notion. Je pense que c’est un peu sec la fin du "Début du voyage".

S’agissant de l’exercice plus qu’il n’y en a qu’un, J’ai remarqué que si dans le fichier on s’amusait a commencer par un espace ou plusieurs, ça faussait les stats. En ajoutant la logique de string_strim pour supprimer tous les espaces au début d’une ligne on résout ce cas. Il doit y avoir moins coûteux, mais je ne m’intéresse pas a obtenir mieux pour l’instant. d’ailleurs je n’est pas passé longtemps a vérifier si cette correction elle même n’avait pas ajoute son lot de probleme mis a part la question des perfs. Je verrais bien une formulation qui encourage a essayer d’ameliorer la logique en suggérant par exemple le cas des espaces en début de ligne et la possibilité de s’appuyer sur une suite d’instruction que nous connaissons déjà ( string_strim)

Édité par mougnolribama

in medio stat virtus

+1 -0

Salut, c’est très cool de prendre le temps de faire ce genre de retour :)

quand je commençais à trouver ça, mais vraiment plus que passionnant, ça s’arrête.

mougnolribama

Que veux tu dire ? Qu’y a-t-il à ajouter ou à approfondir dans ce chapitre ? Tu souhaitais un nouvel exercice de conclusion ou un approfondissement sur une notion que tu n’as pas vraiment saisi avec les explications actuelles ?

Peut être faudrait-il juste que je les considère comme des flux dont je peux extraire des chaines? Ou des flux basés sur des chaines? sauf que les chaines de caractère réside en mémoire centrale. Ca peut paraitre bizarre ce que je vais dire, mais c’est ma façon de simplifier les choses et de comprendre l’utilité des flux. output on extrait quelque chose de la mémoire et input on y range quelque chose. Apres le probleme c’est pas de trop de discuter de la théorie, mais c’est justement qu’après avoir lu Même les chaînes y passent ! je ne vois pas trop l’intérêt, ou plutôt je n’arrive pas a imaginer le(s) cas général(aux) d’utilisation des ifstream.

mougnolribama

Un flux est un niveau d’abstraction comme dit dans le cours, c’est à dire que c’est juste un moyen d’interagir avec une donnée, où qu’elle soit (ça va dépendre du type de flux). Je ne sais pas ce que tu appelle la "mémoire centrale", mais dans le cas d’un fstream la donnée est sur le disque alors que dans le cas d’un stringstream la donnée est en RAM. Le stringstream possède la méthode str() car il manipule une donnée qui est un string, alors par confort il propose une méthode qui permet de la récupérer entièrement, mais c’est une particularité due à sa nature, pas parce que c’est un flux.

Je suppose que tu voulais dire "je ne vois pas trop l’intérêt, ou plutôt je n’arrive pas a imaginer le(s) cas général(aux) d’utilisation des istringstream" pour être cohérent avec ta remarque juste au dessus. Là où on utilise les flux, c’est pour se donner un niveau d’abstraction. Je sais pas si tu as déjà un peu manipuler Linux, mais avec les commandes en console on se rend vite compte que std::cin ne correspond pas forcément au clavier, dans bash tu peux faire en sorte que ce soit la sortie d’un autre programme à l’aide de '|' ou encore tu peux faire en sorte que ce soit le contenu d’un fichier à l’aide de '<’. Eh bien tu peux faire la même chose au niveau de tes fonctions, tu vas pouvoir en concevoir qui reçoivent des données au travers un flux pour les traiter, qu’elles proviennes du clavier, d’un fichier, du réseau … ou bien d’une variable, et c’est dans ce cas où l’appelant de la fonction va pouvoir utiliser le stringstream.

S’agissant de l’exercice plus qu’il n’y en a qu’un, J’ai remarqué que si dans le fichier on s’amusait a commencer par un espace ou plusieurs, ça faussait les stats.

mougnolribama

En fait le bug que tu as trouvé est dûe à l’initialisation de espace_caractere_precedent qui aurait dû être initialisé à true. Tu n’as pas besoin d’enlever les espaces à chaque ligne, ce cas est géré, c’est seulement la première ligne qui pose problème.

jadis @leroivi - all in all we’re just another brick in the wall

+2 -0

Merci @romantik pour toutes tes explications au sujet des stringstream et pour la correction de l’exercice. Il faudrait peut être qu’on m’explique la méthode pour arriver a réaliser ce type d’ajustement. J’ai couru pour ajouter du code la ou il en avait guère besoin.

Que veux tu dire ? Qu’y a-t-il à ajouter ou à approfondir dans ce chapitre ?

romantic

Oui un nouvel exercice. Sur les stringstream sera l’occasion d’approfondir la notion.

Un flux est un niveau d’abstraction comme dit dans le cours, c’est à dire que c’est juste un moyen d’interagir avec une donnée, où qu’elle soit (ça va dépendre du type de flux). Je ne sais pas ce que tu appelle la "mémoire centrale", mais dans le cas d’un fstream la donnée est sur le disque alors que dans le cas d’un stringstream la donnée est en RAM. Le stringstream possède la méthode str() car il manipule une donnée qui est un string, alors par confort il propose une méthode qui permet de la récupérer entièrement, mais c’est une particularité due à sa nature, pas parce que c’est un flux.

romantic

Mémoire central == RAM. stringstream et string sont en mémoire central. OK. Donc c’est peut être ce raccourci de me positionner toujours par rapport a la RAM en parlant de flux (on echangerai systématiquement avec une abstraction de disque, réseau, écran, clavier… Alors que les chaines en RAM peuvent aussi être accéder par des flux) qui me met mal a l’aise avec les stringstream.

D’accord les istringstream on une(des) particularité(s). Je crois que c’est parcequ’ils sont particuliers qu’ils méritent qu’on s’y attarde un peu. Et c’est bien que tu m’es donné toute ses explications en t’appuyant sur le tutoriel que j’ai lu et que je n’est pas très bien compris. Ca prouve qu’il (le tuto) ne fait pas que survoler, mais tente réellement d’expliquer les choses. Pour autant, si il pouvait y avoir un exercice ca serait bien je crois.

Je suppose que tu voulais dire "je ne vois pas trop l’intérêt, ou plutôt je n’arrive pas a imaginer le(s) cas général(aux) d’utilisation des istringstream" pour être cohérent avec ta remarque juste au dessus. Là où on utilise les flux, c’est pour se donner un niveau d’abstraction. Je sais pas si tu as déjà un peu manipuler Linux, mais avec les commandes en console on se rend vite compte que std::cin ne correspond pas forcément au clavier, dans bash tu peux faire en sorte que ce soit la sortie d’un autre programme à l’aide de '|' ou encore tu peux faire en sorte que ce soit le contenu d’un fichier à l’aide de '<’. Eh bien tu peux faire la même chose au niveau de tes fonctions, tu vas pouvoir en concevoir qui reçoivent des données au travers un flux pour les traiter, qu’elles proviennes du clavier, d’un fichier, du réseau … ou bien d’une variable, et c’est dans ce cas où l’appelant de la fonction va pouvoir utiliser le stringstream.

romantic

C’est effectivement de istringstream dont j’ai voulu parler. Les flux a fortiori ce vers des chaines aussi sont donc juste des abstraction qui permettent de manipuler des données indépendamment de la technologie sous-jacente. je crois qu’on peut diificilement faire plus clair ( vu qu’on a pas encore inventé la télépathie ) et quelque part c’était déjà dit en d’autres mot dans le tutoriel. Tes explications m’aide vraiment beaucoup a mieux comprendre cette notion.

J’ai viré windows cette année. Linux est apparemment un passage obligé pour un informaticien. Donc j’y passe et c’est pas désagréable. Tout ça pour dire que ton explications avec les tubes j’ai capté. De toutes façon je pense qu’il y aussi la redirection > sur windows.

Encore merci. Je vais essayer de m’exercer en imaginant d’autre cas où on pourrait avoir besoin de flux vers/sur(?) chaîne. N’hesite pas a m’en proposer. Je doute que tu n’en ai pas.

Édité par mougnolribama

in medio stat virtus

+0 -0

Merci @lmghs. Génération de nom de fichier : je voudrait savoir à quoi ça peut servir. Génération d’identificateur: j’ai peur de comprendre ou de ne pas comprendre.

S’il te plait tu peut expliquer en consiste les deux générations dont tu parles ?

Édité par mougnolribama

in medio stat virtus

+0 -0
void save(std::vector<DesGrosTrucs> const& v, std::string const& prefix)
{
    std::ostringstream oss;
    for( std::size_t i = 0; i != v.size(); ++i)
    {
        oss.str(""); // RAZ
        oss << prefix << i; // possibilité de jouer avec des formats avec une syntaxe dont je ne me souviens jamais
        auto const filename = oss.str();
        std::ofstream f(filename);
        if (!f) throw std::runtime_error("Cannot open file "+filename);
        if (!(f << v[i])) throw std::runtime_error("Cannot store truc in file "+filename);
    }
}

Chaque gros truc est stocké dans un fichier différent dont le nom est fonction de l’indice auquel le truc est stocké dans le tableau.

Édité par lmghs

+1 -0

Merci. C’est vraiment cool. Par contre, on pourrait faire la même chose avec une fonction qui renverrait DesGrosTrucs sous forme de chaine non? On concatene a préfix et ainsi de suite… J’imagine que c’est peut être naïf. Mais le seul avantage que je vois ici, c’est une certaine élégance du code (ça a l’air plus concis avec l’operateur de flux) et j’en suis même pas sûr en fait.

J’ai aussi copié ca sur un forum Java.

Je suis en train de coder un programme pour découper un gros fichier en plusieurs petites parties. Pour cela les parties créées doivent avoir un nom suivante le modèle nomdufichier.part[le suffixe]. La partie suffixe peut être de type numérique ou alphabétique. J’aimerai avoir une option pour définir le nombre de caractères dans le suffixe. C’est-à-dire que si je veux 3 caractères je vais obtenir lenomdufichier.part001, lenomdufichier.part002… et si j’utilise les lettres j’aurai alors lenomdufichier.partaaa, lenomdufichier.partaab, etc… Mais je bloque. La syntaxe Java ne devrait pas me poser problème, je ne vois pas par quel moyen arriver à cela. Avez-vous une idée ?

J’aimerais aussi essayé ça avec string et ostringstream. C’est un de ces cas où les flux sont importants?

Et la génération d’identificateur ?? Tu en as une illustration?

in medio stat virtus

+0 -0

Par contre, on pourrait faire la même chose avec une fonction qui renverrait DesGrosTrucs sous forme de chaine non?

Non. Le GrosTruc est une distraction dans mon exemple. Ce qui est important, c’est le filename = oss.str();. A moins de faire du C (s(n)printf), on ne pouvait pas en C++98/03 faire autrement (et simplement) pour générer des noms de fichiers avec des nombres qui s’incrémentent.

En C++11, on a std::to_string() qui va pouvoir faire la même chose, mais dans une boucle, cela sera moins efficace, et cela ne permettra pas de contrôler correctement le formattage non plus: genre des champs de 4 caractères, commençant au pire par des 0.

Les flux, en mode de base (<<, resp >>), permettent de convertir (/resp décoder) n’importe quoi en (/resp depuis du) texte. Parfois, cette conversion en texte, on ne la veut pas dans des fichiers, mais en mémoire. C’est là que servent les stringstreams.


Pour identificateurs, c’est pareil. Suppose une image de 10 milliards de lignes, et que cela trop gros à traiter d’un coup. A la place, on préfère bosser sur 100 scènes. Pour générer des noms de scènes, on pourra passer par des stringstream de la même façon.

+1 -0

(rien à voir)

Unité de traduction.

Un message sur le discord avec quelqu’un qui inclut un .cpp me fait me demander s’il ne faudrait pas expliquer comment ça marche. Car on donne les règles, mais elles sont noyées sous plein de texte.

Alors, certes on pourrait (devrait, que dis-je?!) faire un résumé en quelques points, mais… ne faudrait-il pas une page dédiée (en annexe?) qui explique les attentes d’un compilateur et comment au vu du C++17 (pré-modules) les choses marchent?

  • qu’à la toute fin (édition des liens),

    • on ne peut pas avoir plusieurs définitions d’une même fonction.
    • que toute fonction employée doit être définie quelque part
  • qu’il y a des étapes intermédiaires (la compilation), où

    • tout symbole employé doit être déclaré en C++ (fonctions, variables globales ; et cas éventuel des déclarations "anticipées" de classes dans un second temps) — ne parlons pas du C, sauf pour dire que le C++ est strict
    • tout type ne peut être défini qu’une seule fois (je crois que pareil pour les fonctions)
    • que tout type manipulé (là, plus d’explications sont requises! ) doit être défini
  • Que ces étapes intermédiaires sont faites sur ce que l’on appelle des unités de traduction.

  • Vu qu’il faudrait dupliquer toutes les déclarations de fonctions, et les définitions de types entre les diverses unités de traduction, on a choisi depuis le C de les factoriser dans des fichiers à part, parce que l’on est des gros fainéants. Ces fichiers sont les fichiers d’en-tête

  • Et donc:

    • pour éviter les duplications de définitions car un même fichier d’en-tête peut être inclus par plusieurs chemins, il y a des gardes anti-réinclusion
    • ce que l’on met dans les fichiers d’en-tête
    • on n’inclut pas les fichiers de définitions (.cpp)
    • les cas particuliers des templates
    • BTW, regardez l’option -E avec gcc et clang

De là découlent les règles pour

  • inline
  • les déclarations de globales (static storage)
  • fonctions et variables statiques dans la portée globale, et espaces de noms anonymes

(et bien d’autres choses à dire que j’ai probablement oubliées)

+3 -0

Je n’avais pas très bien lu. ce que j’aurais du dire c’est qu’on peut caster i ou utiliser std::to_string() et le concatener a prefix. Mais tu sembles dire qu’il y a un problème avec cette façon de faire.

Par contre pour formater finement comme dans l’exemple que tu viens de donner, un champ de 4 caractères commençant au pire par 0, je pourrais envisager que les string soit véritablement insuffisants.

Je tiens a préciser que je ne cherche en aucun cas a démontrer que tu as tort. En réalité, si je suis aussi transparent quant a mon désaccord avec ce que tu m’as expliqué c’est seulement pour que tu puisses mieux évaluer ce qui me bloque. Donc merci de pas te décourager. C’est vraiment important que je comprenne là. Sinon je vais passer la deuxième alors que je sais a peine démarrer.

Édité par mougnolribama

in medio stat virtus

+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