sintaxis programas lenguaje funciones estructuras ejemplos comandos codigos c++ design iostream

c++ - programas - lenguaje c



¿Quién diseñó/diseñó IOStreams de C++, y todavía se consideraría bien diseñado según los estándares actuales? (11)

En primer lugar, puede parecer que estoy pidiendo opiniones subjetivas, pero eso no es lo que busco. Me encantaría escuchar algunos argumentos bien fundamentados sobre este tema.

Con la esperanza de obtener una idea de cómo debería diseñarse un moderno sistema de flujo / serialización, recientemente obtuve una copia del libro Standard C ++ IOStreams and Locales de Angelika Langer y Klaus Kreft . Pensé que si IOStreams no estaba bien diseñado, en primer lugar no habría llegado a la biblioteca estándar de C ++.

Después de haber leído varias partes de este libro, estoy empezando a tener dudas si IOStreams puede comparar, por ejemplo, el STL desde un punto de vista arquitectónico general. Lea, por ejemplo, esta entrevista con Alexander Stepanov (el "inventor" de STL) para conocer algunas decisiones de diseño que entraron en el STL.

Lo que me sorprende en particular :

  • Parece que se desconoce quién fue el responsable del diseño general de IOStreams (me gustaría leer algunos antecedentes sobre esto, ¿alguien sabe buenos recursos?);

  • Una vez que profundizas debajo de la superficie inmediata de IOStreams, por ejemplo, si deseas extender IOStreams con tus propias clases, llegas a una interfaz con nombres de funciones de miembros bastante crípticos y confusos, por ejemplo, uflow / uflow , uflow / underflow , snextc / sbumpc / sgetc / sgetn , pbase / pptr / epptr (y probablemente haya incluso peores ejemplos). Esto hace que sea mucho más difícil comprender el diseño general y cómo las partes individuales cooperan. Incluso el libro que mencioné arriba no ayuda mucho (en mi humilde opinión).

Por lo tanto, mi pregunta:

Si tuviera que juzgar por las normas actuales de ingeniería de software (si realmente hay un acuerdo general sobre ellas), ¿se consideraría que el IOStream de C ++ está bien diseñado? (No me gustaría mejorar mis habilidades de diseño de software a partir de algo que generalmente se considera obsoleto).


Si tuviera que juzgar por las normas actuales de ingeniería de software (si realmente hay un acuerdo general sobre ellas), ¿se consideraría que el IOStream de C ++ está bien diseñado? (No me gustaría mejorar mis habilidades de diseño de software a partir de algo que generalmente se considera obsoleto).

Yo diría que NO , por varias razones:

Mal manejo de errores

Las condiciones de error se deben informar con excepciones, no con el operator void* .

El antipatrón de "objeto zombie" es lo que causa errores como estos .

Mala separación entre formateo y E / S

Esto hace que los objetos de transmisión sean innecesariamente complejos, ya que tienen que contener información de estado adicional para el formateo, ya sea que lo necesite o no.

También aumenta las probabilidades de escribir errores como:

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, en cambio, escribiste algo como:

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

No habría bits de estado relacionados con el formato, y no hay problema.

Tenga en cuenta que en los lenguajes "modernos" como Java, C # y Python, todos los objetos tienen una función toString / ToString / __str__ llamada por las rutinas de E / S. AFAIK, solo C ++ lo hace al revés utilizando stringstream como la forma estándar de convertir a una cadena.

Pobre soporte para i18n

La salida basada en Iostream divide los literales de las cuerdas en pedazos.

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

Las cadenas de formato colocan oraciones completas en literales de cadenas.

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

Este último enfoque es más fácil de adaptar a bibliotecas de internacionalización como GNU gettext, porque el uso de oraciones completas proporciona más contexto para los traductores. Si su rutina de formateo de cadenas admite el reordenamiento (como los parámetros POSIX $ printf), entonces también maneja mejor las diferencias en el orden de las palabras entre los idiomas.


(Esta respuesta solo se basa en mi opinión)

Creo que IOStreams es mucho más complejo que sus funciones equivalentes. Cuando escribo en C ++, sigo usando los encabezados de cstdio para la E / S "antigua", que me parece mucho más predecible. En una nota lateral, (aunque no es realmente importante, la diferencia de tiempo absoluta es insignificante) IOStreams ha demostrado en numerosas ocasiones ser más lento que CI / O.


Con respecto a quién los diseñó, Bjarne Stroustrup creó la biblioteca original (como era de esperar), y luego la reimplementó Dave Preston. Esto fue luego rediseñado y reimplantado una vez más por Jerry Schwartz para Cfront 2.0, utilizando la idea de manipuladores de Andrew Koenig. La versión estándar de la biblioteca se basa en esta implementación.

Fuente "The Design & Evolution of C ++", sección 8.3.1.


Creo que el diseño de IOStreams es brillante en términos de extensibilidad y utilidad.

  1. Buffers de secuencias: eche un vistazo a las extensiones boost.iostream: cree gzip, tee, copie flujos en pocas líneas, cree filtros especiales, etc. No sería posible sin eso.
  2. Integración de localización e integración de formato. Vea lo que se puede hacer:

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

    Puede imprimir: "cien" o incluso:

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

    Puede imprimir "Bonjour" o "בוקר טוב" de acuerdo con la configuración local imbuida de std::cout !

    Tales cosas se pueden hacer solo porque los iostreams son muy flexibles.

¿Podría hacerse mejor?

Por supuesto que podría! De hecho, hay muchas cosas que se podrían mejorar ...

Hoy en día es bastante doloroso derivar correctamente de stream_buffer , no es trivial agregar información de formato adicional a la transmisión, pero es posible.

Pero mirando hacia atrás hace muchos años, todavía el diseño de la biblioteca era lo suficientemente bueno como para traer muchas cosas buenas.

Porque no siempre se puede ver el panorama general, pero si se dejan puntos por extensiones, se obtienen habilidades mucho mejores incluso en puntos en los que no se pensó.


Los iostreams de C ++ tienen muchos defectos, como se señaló en las otras respuestas, pero me gustaría señalar algo en su defensa.

C ++ es prácticamente único entre los lenguajes en uso serio que hace que la entrada y salida variables sean sencillas para los principiantes. En otros idiomas, la entrada del usuario tiende a involucrar la coerción de tipos o formateadores de cadenas, mientras que C ++ hace que el compilador haga todo el trabajo. Lo mismo es mayormente cierto para la salida, aunque C ++ no es tan único en este sentido. Aún así, puede hacer E / S formateadas bastante bien en C ++ sin tener que entender clases y conceptos orientados a objetos, lo cual es pedagógicamente útil y sin tener que entender la sintaxis de los formatos. De nuevo, si estás enseñando a principiantes, eso es una gran ventaja.

Esta simplicidad para principiantes tiene un precio, lo que puede convertirlo en un dolor de cabeza para manejar E / S en situaciones más complejas, pero con suerte el programador ya habrá aprendido lo suficiente como para poder tratar con ellos, o al menos volverse lo suficientemente mayores. beber.


Mi opinión sobre C ++ iostreams ha mejorado sustancialmente con el tiempo, particularmente después de que empecé a extenderlos realmente implementando mis propias clases de transmisión. Comencé a apreciar la extensibilidad y el diseño general, a pesar de los nombres ridículamente pobres de funciones de miembros como xsputn o lo que sea. A pesar de todo, creo que las transmisiones de E / S son una mejora masiva sobre C stdio.h, que no tiene seguridad de tipo y está plagada de importantes fallas de seguridad.

Creo que el principal problema con las secuencias de IO es que combinan dos conceptos relacionados, pero algo ortogonales: el formato textual y la serialización. Por un lado, los flujos de IO están diseñados para producir una representación textual formateada y legible por humanos de un objeto y, por otro lado, para serializar un objeto en un formato portátil. A veces estos dos objetivos son uno y el mismo, pero otras veces esto resulta en incongruencias seriamente molestas. Por ejemplo:

std::stringstream ss; std::string output_string = "Hello world"; ss << output_string; ... std::string input_string; ss >> input_string; std::cout << input_string;

Aquí, lo que recibimos como entrada no es lo que originalmente enviamos a la transmisión. Esto se debe a que el operador << genera la cadena completa, mientras que el operador >> solo leerá de la transmisión hasta que encuentre un carácter de espacio en blanco, ya que no hay información de longitud almacenada en la secuencia. Entonces, aunque generemos un objeto de cadena que contenga "hello world", solo vamos a ingresar un objeto de cadena que contenga "hello". Por lo tanto, aunque la transmisión ha cumplido su función como una función de formato, no ha podido serializar correctamente y luego volver a serializar el objeto.

Se podría decir que las transmisiones IO no se diseñaron para ser instalaciones de serialización, pero si ese es el caso, ¿para qué sirven las transmisiones de entrada ? Además, en la práctica, las transmisiones de E / S se utilizan a menudo para serializar objetos, porque no hay otras instalaciones de serialización estándar. Considere boost::date_time o boost::numeric::ublas::matrix , donde si boost::numeric::ublas::matrix un objeto matrix con el operador << , obtendrá la misma matriz exacta cuando lo ingrese usando el operador >> . Pero para lograr esto, los diseñadores de Boost tuvieron que almacenar el conteo de columnas y la información de recuento de filas como datos de texto en la salida, lo que compromete la visualización real legible por humanos. De nuevo, una combinación incómoda de instalaciones de formato de texto y serialización.

Tenga en cuenta cómo la mayoría de los otros idiomas separan estas dos instalaciones. En Java, por ejemplo, el formateo se realiza a través del método toString() , mientras que la serialización se realiza a través de la interfaz Serializable .

En mi opinión, la mejor solución habría sido introducir transmisiones basadas en bytes , junto con las secuencias basadas en caracteres estándar. Estas secuencias funcionarían con datos binarios, sin preocuparse por el formato / visualización legible por humanos. Podrían usarse únicamente como instalaciones de serialización / deserialización, para traducir objetos C ++ en secuencias de bytes portátiles.


No puedo evitar contestar la primera parte de la pregunta (¿Quién hizo eso?). Pero fue respondida en otros mensajes.

En cuanto a la segunda parte de la pregunta (¿Bien diseñada?), Mi respuesta es un rotundo "¡No!". Aquí un pequeño ejemplo que me hace sacudir la cabeza con incredulidad desde hace años:

#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; }

El código anterior produce tonterías debido al diseño iostream. Por alguna razón más allá de mi alcance, tratan uint8_t bytes como caracteres, mientras que los tipos integrales más grandes se tratan como números. Qed mal diseño.

Tampoco hay manera de que se me ocurra arreglar esto. El tipo podría ser un flotador o un doble en su lugar ... por lo que un lanzamiento a ''int'' para hacer que el iostream tonto entienda que los números, no los caracteres, son el tema no ayudará.

Después de recibir un voto negativo a mi respuesta, tal vez unas pocas palabras más de explicación ... El diseño IOStream es defectuoso, ya que no le da al programador un medio para indicar CÓMO se trata un elemento. La implementación de IOStream toma decisiones arbitrarias (como tratar uint8_t como un carácter de caracteres, no como un número de bytes). Este es un defecto del diseño de IOStream, ya que tratan de lograr lo inalcanzable.

C ++ no permite clasificar un tipo: el idioma no tiene la facilidad. No existe tal cosa como is_number_type () o is_character_type () que IOStream podría usar para hacer una elección automática razonable. Ignorar eso y tratar de salirse con la suya es un defecto de diseño de una biblioteca.

Admitido, printf () tampoco funcionaría en una implementación genérica de "ShowVector ()". Pero eso no es excusa para el comportamiento iostream. Pero es muy probable que en el caso de printf (), ShowVector () se defina así:

template <class _T> void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );


Siempre encontré C ++ IOStreams mal diseñado: su implementación hace que sea muy difícil definir correctamente un nuevo tipo de una secuencia. también mezclan características y características de formateo (piense en manipuladores).

Personalmente, el mejor diseño e implementación de flujos que he encontrado se encuentra en el lenguaje de programación Ada. es un modelo de desacoplamiento, una alegría para crear un nuevo tipo de flujos, y las funciones de salida siempre funcionan independientemente de la secuencia utilizada. esto es gracias a un mínimo común denominador: usted envía bytes a una secuencia y eso es todo. las funciones de flujo se encargan de poner los bytes en la corriente, no es su trabajo, por ejemplo, formatear un entero en hexadecimal (por supuesto, hay un conjunto de atributos de tipo, equivalente a un miembro de clase, definido para manejar el formateo)

ojalá C ++ fuera tan simple con respecto a las transmisiones ...


Siempre me encuentro con sorpresas cuando uso el IOStream.

La biblioteca parece orientada al texto y no binaria. Esa puede ser la primera sorpresa: el uso del indicador binario en las secuencias de archivos no es suficiente para obtener un comportamiento binario. El usuario Charles Salvia arriba lo ha observado correctamente: IOStreams mezcla aspectos de formato (donde desea obtener resultados bonitos, por ejemplo, dígitos limitados para flotantes) con aspectos de serialización (en los que no desea pérdida de información). Probablemente sería bueno separar estos aspectos. Boost.Serialization hace esta mitad. Usted tiene una función de serialización que se dirige a los inserters y extractores si lo desea. Ya tienes la tensión entre ambos aspectos.

Muchas funciones también tienen una semántica confusa (por ejemplo, get, getline, ignore y read. Algunas extraen el delimitador, otras no, y algunas establecen eof). Además, algunos mencionan los nombres de funciones extraños al implementar una secuencia (por ejemplo, xsputn, uflow, underflow). Las cosas empeoran cuando uno usa las variantes wchar_t. El wifstream hace una traducción a multibyte mientras que wstringstream no lo hace. La E / S binaria no funciona de fábrica con wchar_t: tiene que sobrescribir el codecvt.

La E / S en búfer c (es decir, FILE) no es tan poderosa como su contraparte en C ++, pero es más transparente y tiene un comportamiento mucho menos intuitivo.

Sin embargo, cada vez que tropiezo con el IOStream, me atrae como una polilla para disparar. Probablemente sería bueno que un tipo realmente inteligente pudiera echarle un buen vistazo a la arquitectura general.


Varias ideas mal concebidas encontraron su camino en el estándar: auto_ptr , vector<bool> , valarray y export , solo por nombrar algunas. Así que no tomaría necesariamente la presencia de IOStreams como un signo de diseño de calidad.

IOStreams tiene una historia accidentada. En realidad, son una reelaboración de una biblioteca de flujos anterior, pero fueron creados en un momento en que muchas de las expresiones idiomáticas de C ++ de hoy en día no existían, por lo que los diseñadores no tuvieron el beneficio de la retrospectiva. Una cuestión que solo se hizo aparente con el tiempo fue que es casi imposible implementar IOStreams de la misma manera que el stdio de C, debido al abundante uso de funciones virtuales y el reenvío a objetos de búfer internos incluso en la granularidad más fina, y también gracias a una inescrutable rareza en la forma en que se definen e implementan las configuraciones regionales. Mi memoria de esto es bastante confusa, lo admito; Recuerdo que fue objeto de intenso debate hace algunos años, en comp.lang.c ++. Moderado.


Estoy publicando esto como una respuesta separada porque es pura opinión.

La realización de entrada y salida (en particular, de entrada) es un problema muy, muy difícil, por lo que no es sorprendente que la biblioteca de iostremas esté llena de bodges y cosas que con una retrospectiva perfecta podrían haberse hecho mejor. Pero me parece que todas las bibliotecas de E / S, en cualquier idioma, son así. Nunca utilicé un lenguaje de programación donde el sistema de E / S era una belleza que me hizo admirar a su diseñador. La biblioteca iostreams tiene ventajas, particularmente sobre la biblioteca de CI / O (extensibilidad, seguridad de tipo, etc.), pero no creo que nadie la esté retratando como un ejemplo de gran OO o diseño genérico.