Qui a conçu / conçu les stream IOS de C ++, et serait-il toujours considéré comme bien conçu par les normes actuelles?

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


Dans l’espoir d’avoir un aperçu de la manière dont une structure de stream / sérialisation moderne doit être conçue, je me suis récemment procuré une copie du livre Standard C ++ IOStreams et Locales d’Angelika Langer et Klaus Kreft . Je pensais que si IOStreams n’était pas bien conçu, il ne serait pas entré dans la bibliothèque standard C ++ en premier lieu.

Après avoir lu diverses parties de ce livre, je commence à douter que IOStreams puisse se comparer, par exemple, à la STL d’un sharepoint vue architectural global. Lisez, par exemple, cet entretien avec Alexander Stepanov (l’inventeur du TSL) pour en savoir plus sur certaines décisions de conception qui ont été sockets dans le TSL.

Ce qui me surprend en particulier :

  • Il semble être inconnu qui était responsable de la conception globale d’IOStreams (j’aimerais lire des informations de base à ce sujet – est-ce que quelqu’un connaît de bonnes ressources?);

  • Une fois que vous plongez sous la surface immédiate de IOStreams, par exemple si vous souhaitez étendre IOStreams avec vos propres classes, vous obtenez une interface avec des noms de fonctions membres assez cryptiques et confus, par exemple getloc / imbue , sbumpc / sgetc / sgetn , pbase / pptr / epptr (et il y a probablement des exemples encore plus mauvais). Cela rend beaucoup plus difficile la compréhension de la conception globale et de la manière dont les différentes pièces coopèrent. Même le livre que j’ai mentionné ci-dessus n’aide pas beaucoup (IMHO).


Ainsi ma question:

Si vous deviez juger selon les normes d’ingénierie logicielle actuelles (s’il existe effectivement un accord général sur ces normes), les stream IOS 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.)

Plusieurs idées mal conçues se sont retrouvées dans la norme: auto_ptr , vector , valarray et export , pour n’en citer que quelques-unes. Je ne prendrais donc pas forcément la présence de IOStreams comme un signe de qualité.

IOStreams a une histoire à damier. Ils sont en fait une refonte d’une bibliothèque de stream antérieure, mais ils ont été créés à une époque où de nombreux idiomes C ++ n’existaient pas, de sorte que les concepteurs n’avaient pas le recul nécessaire. Un problème qui est devenu évident au fil du temps est qu’il est presque impossible d’implémenter IOStreams aussi efficacement que le stdio C, grâce à l’utilisation abondante de fonctions virtuelles et au transfert des objects à la granularité la plus fine. dans la façon dont les parameters régionaux sont définis et implémentés. Mon souvenir est assez flou, je l’avoue; Je me souviens avoir fait l’object d’intenses débats il y a quelques années, sur comp.lang.c ++.

En ce qui concerne ceux qui les ont conçus, la bibliothèque originale a été (sans surprise) créée par Bjarne Stroustrup, puis réimplémentée par Dave Preston. Ceci a été redessiné et réimplémenté par Jerry Schwartz 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 de C ++”, section 8.3.1.

Si vous deviez juger selon les normes d’ingénierie logicielle actuelles (s’il existe effectivement un accord général sur ces normes), les stream IOS 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 l’ operator void* .

L’anti-pattern “object zombie” est ce qui provoque des bogues comme ceux-ci .

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

Cela rend les objects de stream complexes, 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 comme:

 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 au contraire, 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 pas de problème.

Notez que dans les langages "modernes" comme Java, C # et Python, tous les objects ont une fonction toSsortingng / ToSsortingng / __str__ appelée par les routines d'E / S. AFAIK, seul C ++ le fait en utilisant ssortingngstream 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.

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

Les chaînes de format placent des phrases entières dans des littéraux de chaîne.

 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 telles que GNU gettext, car l'utilisation de phrases entières fournit davantage de contexte aux traducteurs. Si votre routine de formatage de chaînes prend en charge la réorganisation (comme les parameters POSIX $ printf), elle gère également mieux les différences d’ordre des mots entre les langues.

J’affiche 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 difficile, de sorte que la bibliothèque iostreams est remplie de corsages et de choses avec un recul parfait. Mais il me semble que toutes les bibliothèques d’E / S, dans n’importe quelle 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 sur la bibliothèque CI / O (extensibilité, sécurité des types, etc.), mais je ne pense pas que quiconque le considère comme un exemple de conception générique ou OO géniale.

Mon opinion sur C ++ iostreams 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 stream. J’ai commencé à apprécier l’extensibilité et la conception globale, malgré les noms de fonctions membres ridiculement pauvres comme xsputn ou autre. Quoi qu’il en soit, je pense que les stream d’E / S constituent une amélioration considérable par rapport à C stdio.h, qui n’a pas de sécurité de type et qui est entachée de failles de sécurité majeures.

Je pense que le principal problème avec les stream IO est qu’ils associent deux concepts liés mais quelque peu orthogonaux: le formatage textuel et la sérialisation. D’une part, les stream IO sont conçus pour produire une représentation textuelle d’un object, lisible par un humain, et, d’autre part, pour sérialiser un object dans un format portable. Parfois, ces deux objectives ne font qu’un, mais à d’autres moments, il en résulte des incongruités sérieuses. Par exemple:

 std::ssortingngstream ss; std::ssortingng output_ssortingng = "Hello world"; ss << output_string; ... std::string input_string; ss >> input_ssortingng; std::cout << input_string; 

Ici, ce que nous recevons en entrée n’est pas ce que nous avons initialement fourni au stream. En effet, l'opérateur << affiche la chaîne entière, tandis que l'opérateur >> ne lit que depuis le stream jusqu'à ce qu'il rencontre un caractère d'espacement, car il n'y a pas d'informations de longueur stockées dans le stream. Donc, même si nous produisons un object chaîne contenant "hello world", nous allons seulement entrer un object chaîne contenant "hello". Ainsi, bien que le stream ait servi de fonction de formatage, il n'a pas réussi à sérialiser correctement et à désérialiser l'object.

Vous pourriez dire que les stream d'E / S n'ont pas été conçus pour être des fonctions de sérialisation, mais si c'est le cas, à quoi servent vraiment les stream d' entrée ? En outre, dans la pratique, les stream d'E / S sont souvent utilisés pour sérialiser des objects, car il n'existe pas d'autres fonctionnalités de sérialisation standard. Considérez boost::date_time ou boost::numeric::ublas::masortingx , où si vous produisez un object masortingce avec l'opérateur << , vous obtiendrez la même masortingce exacte lorsque vous l'utiliserez en utilisant l'opérateur >> . Mais pour ce faire, les concepteurs Boost ont dû stocker les informations de nombre de colonnes et de nombre de lignes sous forme de données textuelles dans la sortie, ce qui compromet l’écran lisible par l’homme. Encore une fois, une combinaison délicate 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 toSsortingng() , tandis que la sérialisation est effectuée via l'interface Serializable .

À mon avis, la meilleure solution aurait été d'introduire des stream basés sur des octets , parallèlement aux stream standard basés sur des caractères . Ces stream opéreraient sur des données binarys, sans se soucier du formatage / affichage lisible par l'homme. Ils pourraient être utilisés uniquement en tant qu'installations de sérialisation / désérialisation, pour traduire des objects C ++ en séquences d'octets portables.

J’ai toujours trouvé que C ++ IOStreams était mal conçu: leur implémentation rend très difficile de définir correctement un nouveau type de stream. ils mélangent également des fonctionnalités et des fonctionnalités de formatage (pensez aux manipulateurs).

Personnellement, la meilleure conception et implémentation de stream que j’ai jamais trouvée réside dans le langage de programmation Ada. c’est un modèle de découplage, un plaisir de créer un nouveau type de stream, et les fonctions de sortie fonctionnent toujours quel que soit le stream utilisé. c’est grâce à un plus petit dénominateur commun: vous produisez des octets dans un stream et c’est tout. les fonctions de stream prennent soin de mettre les octets dans le stream, ce n’est pas leur tâche de formater par exemple un entier en hexadécimal (bien sûr, il existe un ensemble d’atsortingbuts 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 stream …

Je pense que la conception de IOStreams est géniale en termes d’extensibilité et d’utilité.

  1. Buffers de stream: jetez un coup d’œil aux extensions boost.iostream: créez des fichiers gzip, tee, copiez des stream en quelques lignes, créez des filtres spéciaux, etc. Cela ne serait pas possible sans cela.
  2. Intégration de la localisation et intégration du formatage. Voir 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 parameters régionaux atsortingbués à std::cout !

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

Pourrait-on faire mieux?

Bien sûr que ça pourrait! En fait, il y a beaucoup de choses à améliorer ...

Aujourd'hui, il est très pénible de dériver correctement de stream_buffer , il est tout à fait non sortingvial d'append des informations de formatage supplémentaires à la diffusion, mais possible.

Mais en regardant en arrière il y a de nombreuses années, le design de la bibliothèque était encore assez bon pour apporter de nombreux cadeaux.

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

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

Je pense que les stream IOS sont beaucoup plus complexes que leurs équivalents fonctionnels. Lorsque j’écris en C ++, j’utilise toujours les en-têtes cstdio pour les E / S “anciennes”, que je trouve beaucoup plus prévisibles. Sur un côté, (bien que ce ne soit pas vraiment important, la différence de temps absolue est négligeable) Il a été prouvé à de nombreuses occasions que les stream IOS étaient plus lents que les CI / O.

Je rencontre toujours des sursockets lorsque j’utilise IOStream.

La bibliothèque semble orientée texte et non orientée binary. Cela peut être la première surprise: utiliser l’indicateur binary dans les stream de fichiers n’est pas suffisant pour obtenir un comportement binary. L’utilisateur Charles Salvia ci-dessus l’a bien observé: IOStreams mélange les aspects de formatage (où vous voulez un très bon résultat, par exemple des chiffres limités pour les flottants) avec les aspects de sérialisation (où vous ne voulez pas perdre d’informations). Il serait probablement bon de séparer ces aspects. Boost.Serialization fait cette moitié. Vous avez une fonction de sérialisation qui achemine vers les inserteurs et les extracteurs si vous le souhaitez. Vous avez déjà 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; certaines définissent eof). De plus, certains mentionnent les noms de fonctions étranges lors de l’implémentation d’un stream (par exemple, xsputn, uflow, underflow). Les choses empirent quand on utilise les variantes wchar_t. Le wifstream effectue une traduction en multioctets, contrairement à wssortingngstream. Les entrées / sorties binarys ne fonctionnent pas avec wchar_t: vous avez écrasé le codecvt.

L’E / S en mémoire tampon c (c’est-à-dire FILE) n’est pas aussi puissante que son homologue C ++, mais elle est plus transparente et son comportement intuitif est bien moindre.

Toujours à chaque fois que je tombe sur l’IOStream, je suis attiré par le feu. Ce serait probablement une bonne chose si un type vraiment intelligent avait une bonne idée de l’architecture globale.

Je ne peux m’empêcher de répondre à la première partie de la question (qui a fait cela?). Mais il a été répondu à d’autres postes.

En ce qui concerne la deuxième partie de la question (Bien conçu?), Ma réponse est un “non!” Retentissant. Voici un petit exemple qui me fait trembler la tête d’incrédulité depuis des années:

 #include  #include  #include  // A small attempt in generic programming ;) template  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 byteVector; std::vector wordVector; byteVector.push_back( 42 ); wordVector.push_back( 42 ); ShowVector( "Garbled bytes as characters output oO", 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 de iostream. Pour certaines raisons indépendantes de ma volonté, ils traitent les octets de uint8_t comme des caractères, tandis que les types intégraux plus grands sont traités comme des nombres. Qed Bad design.

Il est également impossible de penser à résoudre ce problème. Le type pourrait tout aussi bien être un flottant ou un double … donc une conversion en “int” pour faire en sorte que iostream soit stupide comprenne que le sujet ne sera pas utile si les nombres ne sont pas des caractères.

Après avoir reçu une réponse négative à ma réponse, peut-être quelques mots d’explication supplémentaires… La conception d’IOStream est erronée car elle ne permet pas au programmeur 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 un nombre d’octets). Ceci est un défaut de la conception IOStream, car ils essaient d’atteindre l’inaccessible.

C ++ ne permet pas de classer un type – le langage n’a pas la facilité. Is_number_type () ou is_character_type () n’existent pas. IOStream pourrait être utilisé 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 () ne fonctionnerait pas non plus dans une implémentation générique “ShowVector ()”. Mais ce n’est pas une excuse pour le comportement de iostream. Mais il est très probable que dans le cas de printf (), ShowVector () soit défini comme ceci:

 template  void ShowVector( const char *formatSsortingng, const char *title, const std::vector<_t> &v ); 

Les iStream C ++ ont beaucoup de failles, comme noté dans les autres réponses, mais j’aimerais noter quelque chose dans sa défense.

C ++ est pratiquement unique parmi les langages en usage sérieux, ce qui rend les entrées et les sorties variables simples pour les débutants. Dans d’autres langages, la saisie de l’utilisateur a tendance à impliquer une contrainte 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. Cependant, vous pouvez très bien faire des E / S formatées en C ++ sans avoir à comprendre les classes et les concepts orientés object, ce qui est utile sur le plan pédagogique et sans avoir à comprendre la syntaxe du format. Encore une fois, si vous enseignez aux débutants, c’est un gros avantage.

Cette simplicité pour les débutants a un prix, ce qui en fait un casse-tête pour gérer les E / S dans des situations plus complexes, mais à ce moment-là, le programmeur a suffisamment appris pour pouvoir les gérer, ou du moins assez vieux. boire.