Qui a conçu / conçu les IOStreams de C ++, et serait-il toujours considéré comme bien conçu selon les normes actuelles? [fermé]

127

Tout d'abord, il peut sembler que je demande des opinions subjectives, mais ce n'est pas ce que je recherche. J'aimerais entendre quelques arguments bien fondés sur ce sujet.


Dans l'espoir d'avoir un aperçu de la façon dont un framework de flux / sérialisation moderne devrait être conçu, je me suis récemment procuré une copie du livre Standard C ++ IOStreams and Locales par Angelika Langer et Klaus Kreft . J'ai pensé que si IOStreams n'était pas bien conçu, il ne l'aurait pas fait dans la bibliothèque standard C ++ en premier lieu.

Après avoir lu différentes parties de ce livre, je commence à avoir des doutes quant à savoir si IOStreams peut se comparer par exemple à la STL d'un point de vue architectural global. Lisez par exemple cet entretien avec Alexander Stepanov («l'inventeur» de la STL) pour en savoir plus sur certaines décisions de conception qui ont été prises dans la STL.

Ce qui me surprend en particulier :

  • Il semble que l'on ne sache pas qui était responsable de la conception générale d'IOStreams (j'aimerais lire quelques informations générales à ce sujet - est-ce que quelqu'un connaît de bonnes ressources?);

  • Une fois que vous avez fouillé sous la surface immédiate d'IOStreams, par exemple si vous souhaitez étendre IOStreams avec vos propres classes, vous accédez à une interface avec des noms de fonctions membres assez cryptiques et déroutants, par exemple getloc/ imbue, uflow/ underflow, snextc/ sbumpc/ sgetc/ sgetn, pbase/ pptr/ epptr(et il y a probablement des exemples encore pires). Cela rend tellement plus difficile de comprendre la conception globale et la façon dont les pièces individuelles coopèrent. Même le livre je l' ai mentionné ci - dessus ne pas l' aide que beaucoup ( à mon humble avis).


Ainsi ma question:

Si vous aviez à en juger par les normes de génie logiciel d'aujourd'hui (s'il en fait est un accord général sur ces), serait C ++ 's iostreams encore être considéré comme bien conçu? (Je ne voudrais pas améliorer mes compétences en conception de logiciels à partir de quelque chose qui est généralement considéré comme obsolète.)

stakx - ne contribue plus
la source
7
Intéressant opinion de Herb Sutter stackoverflow.com/questions/2485963/… :) Dommage que ce gars soit parti après seulement quelques jours de participation
Johannes Schaub - litb
5
Y a-t-il quelqu'un d'autre qui voit un mélange de préoccupations dans les flux STL? Un flux est normalement conçu pour lire ou écrire des octets et rien d'autre. Une chose qui peut lire ou écrire des types de données spécifiques est un formateur (qui peut mais n'a pas besoin d'utiliser un flux pour lire / écrire les octets formatés). Le mélange des deux dans une seule classe rend encore plus complexe la mise en œuvre de ses propres flux.
mmmmmmmm
4
@rsteven, il y a une séparation de ces préoccupations. std::streambufest la classe de base pour la lecture et l'écriture d'octets, et istream/ ostreamest pour l'entrée et la sortie formatées, prenant un pointeur vers std::streambufcomme destination / source.
Johannes Schaub - litb
1
@litb: Mais est-il possible de changer le streambuf qui est utilisé par le flux (formateur)? Alors peut-être que je veux utiliser le formatage STL mais que je veux écrire les données via un streambuf spécifique?
mmmmmmmm
2
@rstevens,ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Johannes Schaub - litb

Réponses:

31

Plusieurs idées mal conçues ont trouvé leur chemin dans la norme: auto_ptr, vector<bool>, valarrayet export, pour ne citer que quelques - uns. Je ne prendrais donc pas nécessairement la présence d'IOStreams comme un signe de conception de qualité.

Les IOStreams ont une histoire mouvementée. Ils sont en fait une refonte d'une ancienne bibliothèque de flux, mais ont été créés à une époque où de nombreux idiomes C ++ actuels n'existaient pas, de sorte que les concepteurs n'ont pas eu le recul. Un problème qui n'est devenu évident qu'au fil du temps était qu'il est presque impossible d'implémenter IOStreams aussi efficacement que le stdio de C, en raison de l'utilisation abondante de fonctions virtuelles et de la transmission aux objets tampons internes avec la plus fine granularité, et aussi grâce à une étrange étrangeté impénétrable. dans la manière dont les locales sont définies et implémentées. Mon souvenir est assez flou, je l'admets; Je me souviens qu'il a fait l'objet d'intenses débats il y a quelques années, sur comp.lang.c ++. Modéré.

Marcelo Cantos
la source
3
Merci pour votre participation. Je vais parcourir l' comp.lang.c++.moderatedarchive et publier des liens au bas de ma question si je trouve quelque chose de précieux. - D'ailleurs, j'ose être en désaccord avec vous sur auto_ptr: après avoir lu le C ++ exceptionnel de Herb Sutter, cela semble être une classe très utile lors de l'implémentation du modèle RAII.
stakx - ne contribue plus
5
@stakx: Néanmoins, il devient obsolète et remplacé par unique_ptrune sémantique plus claire et plus puissante.
UncleBens
3
@UncleBens unique_ptrnécessite une référence rvalue. Donc, à ce stade, auto_ptrc'est un pointeur très puissant.
Artyom
7
Mais auto_ptra vissé la sémantique de copie / affectation qui en fait une niche pour déréférencer les bogues ...
Matthieu M.
5
@TokenMacGuy: ce n'est pas un vecteur, et il ne stocke pas de booléens. Ce qui le rend quelque peu trompeur. ;)
jalf
40

Quant à savoir qui les a conçus, la bibliothèque originale a été (sans surprise) créée par Bjarne Stroustrup, puis réimplémentée par Dave Presotto. Cela a ensuite été repensé et réimplémenté une fois de plus par Jerry Schwarz pour Cfront 2.0, en utilisant l'idée de manipulateurs d'Andrew Koenig. La version standard de la bibliothèque est basée sur cette implémentation.

Source "La conception et l'évolution du C ++", section 8.3.1.

Quuxplusone
la source
3
@Neil - quel est votre avis sur le design? Sur la base de vos autres réponses, beaucoup de gens aimeraient entendre votre opinion ...
DVK
1
@DVK Je viens de publier mon opinion sous forme de réponse distincte.
2
Je viens de trouver une transcription d'une interview avec Bjarne Stroustrup où il mentionne quelques bribes de l'histoire d' IOStreams : www2.research.att.com/~bs/01chinese.html (ce lien semble être temporairement rompu en ce moment, mais vous pouvez essayer Cache de page de Google)
stakx - ne contribue plus
2
Lien mis à jour: stroustrup.com/01chinese.html .
FrankHB
28

Si vous deviez juger d'après les normes d'ingénierie logicielle d'aujourd'hui (s'il y a réellement un accord général sur celles-ci), les IOStreams de C ++ seraient-ils toujours considérés comme bien conçus? (Je ne voudrais pas améliorer mes compétences en conception de logiciels à partir de quelque chose qui est généralement considéré comme obsolète.)

Je dirais NON , pour plusieurs raisons:

Mauvaise gestion des erreurs

Les conditions d'erreur doivent être signalées avec des exceptions, pas avec operator void*.

L'anti-pattern "objet zombie" est ce qui cause des bugs comme ceux-ci .

Mauvaise séparation entre le formatage et les E / S

Cela rend les objets de flux inutiles, car ils doivent contenir des informations d'état supplémentaires pour le formatage, que vous en ayez besoin ou non.

Cela augmente également les chances d'écrire des bogues tels que:

using namespace std; // I'm lazy.
cout << hex << setw(8) << setfill('0') << x << endl;
// Oops!  Forgot to set the stream back to decimal mode.

Si à la place, vous avez écrit quelque chose comme:

cout << pad(to_hex(x), 8, '0') << endl;

Il n'y aurait pas de bits d'état liés au formatage et aucun problème.

Notez que dans les langages «modernes» comme Java, C # et Python, tous les objets ont une fonction toString/ ToString/ __str__qui est appelée par les routines d'E / S. AFAIK, seul C ++ le fait dans l'autre sens en utilisant stringstreamcomme méthode standard de conversion en chaîne.

Mauvais support pour i18n

La sortie basée sur Iostream divise les littéraux de chaîne en morceaux.

cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;

Les chaînes de format mettent des phrases entières dans des chaînes littérales.

printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);

Cette dernière approche est plus facile à adapter aux bibliothèques d'internationalisation comme GNU gettext, car l'utilisation de phrases entières fournit plus de contexte pour les traducteurs. Si votre routine de formatage de chaînes prend en charge la réorganisation (comme les $paramètres printf POSIX ), elle gère également mieux les différences d'ordre des mots entre les langues.

dan04
la source
4
En fait, pour i18n, les remplacements doivent être identifiés par des positions (% 1,% 2, ..), car une traduction peut nécessiter de modifier l'ordre des paramètres. Sinon, je suis entièrement d'accord - +1.
peterchen
4
@peterchen: C'est à cela que servent les $spécificateurs POSIX printf.
jamesdlin
2
Le problème n'est pas le format des chaînes, c'est que C ++ a des varargs non sécurisés.
dan04
5
Depuis C ++ 11, il dispose désormais de varargs sécurisés.
Mooing Duck
2
IMHO les «informations supplémentaires sur l'état» est le pire problème. cout est un mondial; y attacher des indicateurs de mise en forme rend ces indicateurs globaux, et si l'on considère que la plupart de leurs utilisations ont une portée prévue de quelques lignes, c'est assez horrible. Il aurait été possible de corriger cela avec une classe 'formatter', qui se lie à un ostream mais garde son propre état. Et, les choses faites avec cout semblent généralement terribles par rapport à la même chose faite avec printf (quand c'est possible) ..
greggo
17

Je publie ceci comme une réponse séparée parce que c'est une opinion pure.

Effectuer des entrées et des sorties (en particulier des entrées) est un problème très, très difficile, donc sans surprise, la bibliothèque iostreams est pleine de corps et de choses qui, avec un recul parfait, auraient pu être mieux faites. Mais il me semble que toutes les bibliothèques d'E / S, quelle que soit la langue, sont comme ça. Je n'ai jamais utilisé un langage de programmation où le système d'E / S était une chose de beauté qui m'a fait admirer son concepteur. La bibliothèque iostreams présente des avantages, en particulier par rapport à la bibliothèque CI / O (extensibilité, sécurité de type, etc.), mais je ne pense pas que quiconque la présente comme un exemple de grande OO ou de conception générique.


la source
16

Mon opinion sur les iostreams C ++ s'est considérablement améliorée au fil du temps, en particulier après avoir commencé à les étendre en implémentant mes propres classes de flux. J'ai commencé à apprécier l'extensibilité et la conception générale, malgré les noms de fonctions des membres ridiculement pauvres comme xsputnou autre. Quoi qu'il en soit, je pense que les flux d'E / S sont une amélioration considérable par rapport à C stdio.h, qui n'a pas de sécurité de type et est criblé de failles de sécurité majeures.

Je pense que le principal problème avec les flux IO est qu'ils confondent deux concepts liés mais quelque peu orthogonaux: le formatage textuel et la sérialisation. D'une part, les flux d'E / S sont conçus pour produire une représentation textuelle formatée et lisible par l'homme d'un objet et, d'autre part, pour sérialiser un objet dans un format portable. Parfois, ces deux objectifs sont identiques, mais d'autres fois, cela se traduit par des incongruités très ennuyeuses. Par exemple:

std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;

...

std::string input_string;
ss >> input_string;
std::cout << input_string;

Ici, ce que nous obtenons en entrée n'est pas ce que nous avons initialement émis dans le flux. C'est parce que l' <<opérateur sort la chaîne entière, alors que l' >>opérateur ne lira que dans le flux jusqu'à ce qu'il rencontre un caractère d'espacement, car aucune information de longueur n'est stockée dans le flux. Ainsi, même si nous sortons un objet string contenant "hello world", nous n'entrerons qu'un objet string contenant "hello". Ainsi, bien que le flux ait rempli son rôle de fonction de formatage, il n'a pas réussi à sérialiser correctement puis désérialiser l'objet.

Vous pourriez dire que les flux d'E / S n'ont pas été conçus pour être des installations de sérialisation, mais si c'est le cas, à quoi servent vraiment les flux d' entrée ? En outre, dans la pratique, les flux d'E / S sont souvent utilisés pour sérialiser des objets, car il n'y a pas d'autres fonctionnalités de sérialisation standard. Considérez boost::date_timeou boost::numeric::ublas::matrix, où si vous sortez un objet matrice avec l' <<opérateur, vous obtiendrez la même matrice exacte lorsque vous l'entrerez à l'aide de l' >>opérateur. Mais pour ce faire, les concepteurs de Boost ont dû stocker des informations sur le nombre de colonnes et de lignes sous forme de données textuelles dans la sortie, ce qui compromet l'affichage réel lisible par l'homme. Encore une fois, une combinaison maladroite de fonctionnalités de formatage textuel et de sérialisation.

Notez comment la plupart des autres langues séparent ces deux fonctionnalités. En Java, par exemple, le formatage est effectué via la toString()méthode, tandis que la sérialisation est effectuée via l' Serializableinterface.

À mon avis, la meilleure solution aurait été d'introduire des flux basés sur des octets , parallèlement aux flux basés sur des caractères standard . Ces flux fonctionneraient sur des données binaires, sans souci de formatage / affichage lisible par l'homme. Ils pourraient être utilisés uniquement comme outils de sérialisation / désérialisation, pour traduire des objets C ++ en séquences d'octets portables.

Charles Salvia
la source
Merci de répondre. Je pourrais bien me tromper à ce sujet, mais concernant votre dernier point (flux basés sur les octets ou sur les caractères), la réponse (partielle?) D'IOStream à cela n'est-elle pas la séparation entre les tampons de flux (conversion de caractères, transport et mise en mémoire tampon) et les flux (formatage / analyse)? Et ne pourriez-vous pas créer de nouvelles classes de flux, celles qui sont destinées uniquement à la sérialisation et à la désérialisation (lisibles par machine) et d'autres qui sont uniquement destinées au formatage et à l'analyse (lisibles par l'homme)?
stakx - ne contribue plus
@stakx, oui, et en fait, je l'ai fait. C'est un peu plus ennuyeux qu'il n'y paraît, car std::char_traitsil ne peut pas être spécialisé de manière portable pour prendre un fichier unsigned char. Cependant, il existe des solutions de contournement, donc je suppose que l'extensibilité vient à nouveau à la rescousse. Mais je pense que le fait que les flux basés sur les octets ne soient pas standard est une faiblesse de la bibliothèque.
Charles Salvia
4
De plus, l'implémentation de flux binaires vous oblige à implémenter de nouvelles classes de flux et de nouvelles classes de tampon, car les problèmes de formatage ne sont pas entièrement séparés de std::streambuf. Donc, fondamentalement, la seule chose que vous étendez est la std::basic_iosclasse. Il y a donc une ligne où «étendre» croise le territoire de «réimplémentation complète», et la création d'un flux binaire à partir des fonctions de flux d'E / S C ++ semble approcher ce point.
Charles Salvia
bien dit et exactement ce que je soupçonnais. Et le fait que C et C ++ se donnent beaucoup de mal pour ne pas donner de garanties sur des largeurs de bits et des représentations spécifiques peut en effet devenir problématique lorsqu'il s'agit de faire des E / S.
stakx - ne contribue plus
" pour sérialiser un objet dans un format portable. " non, ils n'ont jamais été destinés à supporter cela
curiousguy
11

J'ai toujours trouvé les IOStreams C ++ mal conçus: leur implémentation rend très difficile la définition correcte d'un nouveau type de flux. ils mélangent également des fonctionnalités io et des fonctionnalités de formatage (pensez aux manipulateurs).

personnellement, la meilleure conception et implémentation de flux que j'ai jamais trouvée réside dans le langage de programmation Ada. c'est un modèle de découplage, une joie de créer de nouveaux types de flux, et les fonctions de sortie fonctionnent toujours quel que soit le flux utilisé. c'est grâce à un plus petit dénominateur commun: vous sortez des octets dans un flux et c'est tout. les fonctions de flux prennent soin de mettre les octets dans le flux, ce n'est pas leur travail par exemple de formater un entier en hexadécimal (bien sûr, il existe un ensemble d'attributs de type, équivalent à un membre de classe, défini pour gérer le formatage)

Je souhaite que C ++ soit aussi simple en ce qui concerne les flux ...

Adrien Plisson
la source
Le livre que j'ai mentionné explique l'architecture de base d'IOStreams comme suit: Il existe une couche de transport (les classes de tampon de flux) et une couche d'analyse / formatage (les classes de flux). Les premiers sont responsables de la lecture / écriture des caractères depuis / vers un flux d'octets, tandis que les seconds sont responsables de l'analyse des caractères ou de la sérialisation des valeurs en caractères. Cela semble assez clair, mais je ne suis pas sûr que ces préoccupations soient vraiment clairement séparées dans la réalité, en particulier. lorsque les paramètres régionaux entrent en jeu. - Je suis également d'accord avec vous sur la difficulté d'implémenter de nouvelles classes de flux.
stakx - ne contribue plus
"mix io ​​features and formatting features" <- Qu'est-ce qui ne va pas? C'est un peu le but de la bibliothèque. En ce qui concerne la création de nouveaux flux, vous devez créer un streambuf au lieu d'un flux et construire un flux simple autour du streambuf.
Billy ONeal
il semble que les réponses à cette question m'ont fait comprendre quelque chose qui ne m'a jamais été expliqué: je devrais dériver un streambuf au lieu d'un stream ...
Adrien Plisson
@stakx: Si le calque streambuf faisait ce que vous avez dit, ce serait bien. Mais la conversion entre la séquence de caractères et l'octet sont toutes mélangées avec les E / S réelles (fichier, console, etc.). Il n'y a aucun moyen d'effectuer les E / S de fichier sans effectuer également la conversion de caractères, ce qui est très malheureux.
Ben Voigt
10

Je pense que la conception d'IOStreams est brillante en termes d'extensibilité et d'utilité.

  1. Tampons de flux: jetez un œil aux extensions boost.iostream: créez des gzip, des tee, copiez des flux en quelques lignes, créez des filtres spéciaux, etc. Ce ne serait pas possible sans cela.
  2. Intégration de localisation et intégration de formatage. Voyez ce qui peut être fait:

    std::cout << as::spellout << 100 << std::endl;

    Peut imprimer: "cent" ou même:

    std::cout << translate("Good morning")  << std::endl;

    Peut imprimer "Bonjour" ou "בוקר טוב" selon les paramètres régionaux imprégnés std::cout!

    De telles choses peuvent être faites simplement parce que les iostreams sont très flexibles.

Cela pourrait-il être mieux fait?

Bien sûr que ça pourrait! En fait, il y a beaucoup de choses qui pourraient être améliorées ...

Aujourd'hui, il est assez pénible de dériver correctement stream_buffer, il n'est pas anodin d'ajouter des informations de formatage supplémentaires au flux, mais c'est possible.

Mais en regardant en arrière il y a de nombreuses années, la conception de la bibliothèque était encore assez bonne pour être sur le point d'apporter de nombreux avantages.

Parce que vous ne pouvez pas toujours voir la situation dans son ensemble, mais si vous laissez des points pour les extensions, cela vous donne de bien meilleures capacités même dans des points auxquels vous n'avez pas pensé.

Artyom
la source
5
Pouvez-vous expliquer pourquoi vos exemples pour le point 2 seraient meilleurs que d'utiliser simplement quelque chose comme print (spellout(100));et print (translate("Good morning"));Cela semblerait être une bonne idée, car cela dissocie le formatage et i18n des E / S.
Schedler
3
Parce qu'il peut être traduit en fonction de la langue imprégnée dans stream. c'est-à-dire french_output << translate("Good morning"):; english_output << translate("Good morning") vous donnerait: "Bonjour Good morning"
Artyom
3
La localisation est beaucoup plus difficile lorsque vous devez faire '<< "texte" << valeur' ​​dans une langue mais '<< valeur << "texte"' dans une autre - par rapport à printf
Martin Beckett
@Martin Beckett Je sais, jetez un œil à la bibliothèque Boost.Locale, ce qui se passe dans un tel cas out << format("text {1}") % valueet cela peut être traduit "{1} translated". Donc ça marche bien ;-).
Artyom
15
Ce qui «peut être fait» n'est pas très pertinent. Vous êtes un programmeur, tout peut être fait avec suffisamment d'efforts. Mais avec IOStreams, il est extrêmement difficile de réaliser la plupart de ce qui peut être fait . Et vous obtenez généralement des performances médiocres pour votre problème.
jalf
2

(Cette réponse est juste basée sur mon opinion)

Je pense que les IOStreams sont beaucoup plus complexes que leurs équivalents de fonction. Quand j'écris en C ++, j'utilise toujours les en-têtes cstdio pour les E / S "à l'ancienne", que je trouve beaucoup plus prévisibles. Par ailleurs, (bien que ce ne soit pas vraiment important; le décalage horaire absolu est négligeable) il a été prouvé à de nombreuses reprises que les flux IOS sont plus lents que CI / O.

Delan Azabani
la source
Je pense que vous voulez dire «fonction» plutôt que «fonctionnel». la programmation fonctionnelle produit du code encore pire que la programmation générique.
Chris Becke
Merci d'avoir signalé cette erreur; J'ai modifié la réponse pour refléter la correction.
Delan Azabani
5
IOStreams devrait presque certainement être plus lent que le stdio classique; si on m'avait confié la tâche de concevoir un cadre de flux d'E / S extensible et facile à utiliser, je jugerais probablement la vitesse secondaire, étant donné que les véritables goulots d'étranglement seront probablement la vitesse d'E / S des fichiers ou la bande passante du trafic réseau.
stakx - ne contribue plus
1
Je conviens que pour les E / S ou le réseau, la vitesse de calcul n'a pas beaucoup d'importance. Cependant, rappelez-vous que le C ++ pour la conversion numérique / chaîne utilise sstringstream. Je pense que la vitesse compte, même si c'est secondaire.
Matthieu M.
1
Les E / S des fichiers @stakx et les goulots d'étranglement du réseau sont fonction des coûts «par octet» qui sont assez faibles et considérablement réduits par les améliorations technologiques. De plus, étant donné le DMA, ces frais généraux ne réduisent pas le temps processeur des autres threads sur la même machine. Ainsi, si vous effectuez une sortie formatée, le coût de le faire de manière efficace ou non peut facilement être important (du moins, pas éclipsé par le disque ou le réseau; il est plus probable qu'il soit éclipsé par d'autres traitements dans l'application).
greggo
2

Je rencontre toujours des surprises lors de l'utilisation de l'IOStream.

La bibliothèque semble orientée texte et non orientée binaire. Cela peut être la première surprise: l'utilisation de l'indicateur binaire dans les flux de fichiers n'est pas suffisante pour obtenir un comportement binaire. L'utilisateur Charles Salvia ci-dessus l'a observé correctement: IOStreams mélange les aspects de formatage (où vous voulez une jolie sortie, par exemple des chiffres limités pour les flottants) avec des aspects de sérialisation (où vous ne voulez pas de perte d'informations). Il serait probablement bon de séparer ces aspects. La sérialisation fait cette moitié. Vous disposez d'une fonction de sérialisation qui achemine vers les inserteurs et extracteurs si vous le souhaitez. Là déjà vous avez la tension entre les deux aspects.

De nombreuses fonctions ont également une sémantique déroutante (par exemple, get, getline, ignore et read. Certaines extraient le délimiteur, d'autres non; aussi certains set eof). Plus loin, certains mentionnent les noms de fonction étranges lors de l'implémentation d'un flux (par exemple xsputn, uflow, underflow). Les choses empirent encore lorsque l'on utilise les variantes wchar_t. Le wifstream effectue une traduction en multi-octets alors que wstringstream ne le fait pas. Les E / S binaires ne fonctionnent pas directement avec wchar_t: vous avez l'écrasement du codecvt.

L'E / S tamponnée c (c'est-à-dire FILE) n'est pas aussi puissante que son homologue C ++, mais est plus transparente et a un comportement beaucoup moins contre-intuitif.

Pourtant, chaque fois que je tombe sur l'IOStream, je suis attiré par celui-ci comme un papillon de nuit qui tire. Ce serait probablement une bonne chose si un type vraiment intelligent avait un bon aperçu de l'architecture globale.

gast128
la source
1

Je ne peux m'empêcher de répondre à la première partie de la question (Qui a fait ça?). Mais il a été répondu dans d'autres messages.

Quant à la deuxième partie de la question (bien conçue?), Ma réponse est un «non!» Retentissant. Voici un petit exemple qui me fait secouer la tête d'incrédulité depuis des années:

#include <stdint.h>
#include <iostream>
#include <vector>

// A small attempt in generic programming ;)
template <class _T>
void ShowVector( const char *title, const std::vector<_T> &v)
{
    std::vector<_T>::const_iterator iter;
    std::cout << title << " (" << v.size() << " elements): ";
    for( iter = v.begin(); iter != v.end(); ++iter )
    {
        std::cout << (*iter) << " ";
    }
    std::cout << std::endl;
}
int main( int argc, const char * argv[] )
{
    std::vector<uint8_t> byteVector;
    std::vector<uint16_t> wordVector;
    byteVector.push_back( 42 );
    wordVector.push_back( 42 );
    ShowVector( "Garbled bytes as characters output o.O", byteVector );
    ShowVector( "With words, the numbers show as numbers.", wordVector );
    return 0;
}

Le code ci-dessus produit un non-sens en raison de la conception iostream. Pour certaines raisons hors de ma portée, ils traitent les octets uint8_t comme des caractères, tandis que les types intégraux plus grands sont traités comme des nombres. Conception de Qed Bad.

Je ne peux pas non plus penser à résoudre ce problème. Le type pourrait aussi bien être un float ou un double à la place ... donc un cast en 'int' pour faire comprendre à iostream idiot que les nombres et non les caractères sont le sujet n'aidera pas.

Après avoir reçu un vote défavorable pour ma réponse, peut-être quelques mots d'explication supplémentaires ... La conception d'IOStream est imparfaite car elle ne donne pas au programmeur un moyen d'indiquer COMMENT un élément est traité. L'implémentation IOStream prend des décisions arbitraires (comme traiter uint8_t comme un caractère, pas comme un nombre d'octets). C'est un défaut de la conception IOStream, car ils essaient de réaliser l'irréalisable.

C ++ ne permet pas de classer un type - le langage n'a pas la possibilité. Il n'y a pas de chose telle que is_number_type () ou is_character_type () IOStream pourrait utiliser pour faire un choix automatique raisonnable. Ignorer cela et essayer de s'en sortir en devinant EST un défaut de conception d'une bibliothèque.

Admis, printf () échouerait également à fonctionner dans une implémentation générique "ShowVector ()". Mais ce n'est pas une excuse pour le comportement iostream. Mais il est très probable que dans le cas de printf (), ShowVector () serait défini comme ceci:

template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );
BitTickler
la source
3
Le blâme ne repose pas (purement) sur iostream. Vérifiez à quoi uint8_tsert votre typedef . Est-ce vraiment un char? Alors ne blâmez pas iostreams de le traiter comme un personnage.
Martin Ba
Et si vous voulez vous assurer que vous obtenez un nombre dans le code générique, vous pouvez utiliser la num_putfacette au lieu de l'opérateur d'insertion de flux.
Martin Ba
@Martin Ba Vous avez raison - les normes c / c ++ permettent de garder ouvert le nombre d'octets d'un "short unsigned int". "unsigned char" est une idiosyncrasie de la langue. Si vous voulez vraiment un octet, vous devez utiliser un caractère non signé. C ++ ne permet pas non plus d'imposer des restrictions sur les arguments de modèle - tels que "seulement des nombres" et donc si je changeais l'implémentation de ShowVector en votre solution num_put proposée, ShowVector ne pourrait plus afficher un vecteur de chaînes, non? ;)
BitTickler
1
@Martin Bla: cppreference mentionne que int8_t est un type entier signé avec une largeur d'exactement 8 bits.Je suis d'accord avec l'auteur pour dire qu'il est étrange que vous obteniez une sortie de déchets alors, bien que cela soit techniquement explicable par le typedef et la surcharge des types char dans iostream . Cela aurait pu être résolu en ayant un __int8 un vrai type au lieu d'un typedef.
gast128
Oh, c'est en fait assez facile à corriger: // Corrections pour std :: ostream qui a interrompu le support des types unsigned / signed / char // et imprime les entiers 8 bits comme s'ils étaient des caractères. namespace ostream_fixes {inline std :: ostream & operator << (std :: ostream & os, unsigned char i) {return os << static_cast <unsigned int> (i); } inline std :: ostream & operator << (std :: ostream & os, signé char i) {return os << static_cast <signed int> (i); }} // namespace ostream_fixes
mcv
1

Les iostreams C ++ ont beaucoup de défauts, comme indiqué dans les autres réponses, mais j'aimerais noter quelque chose pour sa défense.

Le C ++ est pratiquement unique parmi les langages largement utilisés, ce qui rend l'entrée et la sortie variables simples pour les débutants. Dans d'autres langages, l'entrée utilisateur a tendance à impliquer la coercition de type ou des formateurs de chaînes, tandis que C ++ fait tout le travail du compilateur. La même chose est largement vraie pour la sortie, bien que C ++ ne soit pas aussi unique à cet égard. Pourtant, vous pouvez assez bien faire des E / S formatées en C ++ sans avoir à comprendre les classes et les concepts orientés objet, ce qui est pédagogiquement utile, et sans avoir à comprendre la syntaxe de format. Encore une fois, si vous enseignez aux débutants, c'est un gros plus.

Cette simplicité pour les débutants a un prix, ce qui peut en faire un casse-tête pour gérer les E / S dans des situations plus complexes, mais j'espère qu'à ce stade, le programmeur en aura suffisamment appris pour être en mesure de les gérer, ou du moins vieillir suffisamment boire.

utilisateur2310967
la source