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.)
std::streambuf
est la classe de base pour la lecture et l'écriture d'octets, etistream
/ostream
est pour l'entrée et la sortie formatées, prenant un pointeur versstd::streambuf
comme destination / source.ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Réponses:
Plusieurs idées mal conçues ont trouvé leur chemin dans la norme:
auto_ptr
,vector<bool>
,valarray
etexport
, 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é.
la source
comp.lang.c++.moderated
archive 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 surauto_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.unique_ptr
une sémantique plus claire et plus puissante.unique_ptr
nécessite une référence rvalue. Donc, à ce stade,auto_ptr
c'est un pointeur très puissant.auto_ptr
a vissé la sémantique de copie / affectation qui en fait une niche pour déréférencer les bogues ...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.
la source
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:
Si à la place, vous avez écrit quelque chose comme:
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 utilisantstringstream
comme 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.
Les chaînes de format mettent des phrases entières dans des chaînes littérales.
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.la source
$
spécificateurs POSIXprintf
.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
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
xsputn
ou 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:
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_time
ouboost::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'Serializable
interface.À 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.
la source
std::char_traits
il ne peut pas être spécialisé de manière portable pour prendre un fichierunsigned 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.std::streambuf
. Donc, fondamentalement, la seule chose que vous étendez est lastd::basic_ios
classe. 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.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 ...
la source
Je pense que la conception d'IOStreams est brillante en termes d'extensibilité et d'utilité.
Intégration de localisation et intégration de formatage. Voyez ce qui peut être fait:
Peut imprimer: "cent" ou même:
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é.
la source
print (spellout(100));
etprint (translate("Good morning"));
Cela semblerait être une bonne idée, car cela dissocie le formatage et i18n des E / S.french_output << translate("Good morning")
:;english_output << translate("Good morning")
vous donnerait: "Bonjour Good morning"out << format("text {1}") % value
et cela peut être traduit"{1} translated"
. Donc ça marche bien;-)
.(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.
la source
sstringstream
. Je pense que la vitesse compte, même si c'est secondaire.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.
la source
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:
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:
la source
uint8_t
sert votre typedef . Est-ce vraiment un char? Alors ne blâmez pas iostreams de le traiter comme un personnage.num_put
facette au lieu de l'opérateur d'insertion de flux.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.
la source