En primer lugar, puede parecer que estoy pidiendo opiniones subjetivas, pero eso no es lo que busco. Me encantaría escuchar algunos argumentos bien fundados sobre este tema.
Con la esperanza de obtener una idea de cómo debe diseñarse un marco de transmisión / serialización moderno, recientemente obtuve una copia del libro Standard C ++ IOStreams and Locales de Angelika Langer y Klaus Kreft . Pensé que si IOStreams no estuviera bien diseñado, no habría llegado a la biblioteca estándar de C ++ en primer lugar.
Después de haber leído varias partes de este libro, estoy empezando a tener dudas sobre si IOStreams puede compararse, por ejemplo, con el STL desde un punto de vista arquitectónico general. Lea, por ejemplo, esta entrevista con Alexander Stepanov (el "inventor" de la STL) para conocer algunas decisiones de diseño que entraron en la STL.
Lo que me sorprende en particular :
Parece que se desconoce quién fue el responsable del diseño general de IOStreams (me encantaría leer algunos antecedentes sobre esto: ¿alguien conoce buenos recursos?);
Una vez que profundiza debajo de la superficie inmediata de IOStreams, por ejemplo, si desea extender IOStreams con sus propias clases, llega a una interfaz con nombres de funciones de miembro bastante crípticos y confusos, por ejemplo
getloc
/imbue
,uflow
/underflow
,snextc
/sbumpc
/sgetc
/sgetn
,pbase
/pptr
/epptr
(y hay probablemente incluso peores ejemplos). Esto hace que sea mucho más difícil comprender el diseño general y cómo cooperan las piezas individuales. Incluso el libro que he mencionado anteriormente no ayuda a que mucho (en mi humilde opinión).
Por lo tanto mi pregunta:
Si tuviera que juzgar según los estándares de ingeniería de software de hoy (si realmente hay algún acuerdo general sobre estos), ¿los IOStreams de C ++ aún se considerarían bien diseñados? (No me gustaría mejorar mis habilidades de diseño de software de algo que generalmente se considera obsoleto).
std::streambuf
es la clase base para leer y escribir bytes, yistream
/ostream
es para formato de entrada y salida, tomando un punterostd::streambuf
como destino / fuente.ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Respuestas:
Varias ideas mal concebidas encontraron su camino en la norma:
auto_ptr
,vector<bool>
,valarray
yexport
, sólo para nombrar unos pocos. Por lo tanto, no tomaría la presencia de IOStreams necesariamente como un signo de diseño de calidad.IOStreams tiene un historial a cuadros. En realidad, son una reelaboración de una biblioteca de secuencias anterior, pero se crearon en un momento en que muchas de las expresiones idiomáticas de C ++ de la actualidad no existían, por lo que los diseñadores no tuvieron el beneficio de la retrospectiva. Un problema que solo se hizo evidente con el tiempo fue que es casi imposible implementar IOStreams de manera tan eficiente como el estándar de C, debido al uso copioso de funciones virtuales y al reenvío a objetos de almacenamiento intermedio interno incluso con la mayor granularidad, y también gracias a cierta extrañeza inescrutable 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 un intenso debate hace algunos años, en comp.lang.c ++. Moderado.
fuente
comp.lang.c++.moderated
archivo y publicaré enlaces al final de mi pregunta si encuentro algo valioso. - Además, me atrevo a estar en desacuerdo con usted sobreauto_ptr
: Después de leer C ++ excepcional de Herb Sutter, parece una clase muy útil al implementar el patrón RAII.unique_ptr
una semántica más clara y poderosa.unique_ptr
requiere una referencia de valor. Entonces en este puntoauto_ptr
es un puntero muy poderoso.auto_ptr
ha jodido la semántica de copia / asignación que lo convierte en un nicho para eliminar errores de referencia ...Con respecto a quién los diseñó, la biblioteca original fue (no sorprendentemente) creada por Bjarne Stroustrup, y luego reimplementada por Dave Presotto. Esto fue rediseñado y reimplementado nuevamente por Jerry Schwarz 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 "El diseño y la evolución de C ++", sección 8.3.1.
fuente
Yo diría que NO , por varias razones:
Mal manejo de errores
Las condiciones de error deben informarse con excepciones, no con
operator void*
.El antipatrón "objeto zombie" es lo que causa errores como estos .
Mala separación entre formateo y E / S
Esto hace que los objetos continuos sean innecesariamente complejos, ya que tienen que contener información de estado adicional para formatear, ya sea que lo necesite o no.
También aumenta las probabilidades de escribir errores como:
Si en cambio, escribiste algo como:
No habría bits de estado relacionados con el formato y no habría problema.
Tenga en cuenta que en 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 al usarlostringstream
como la forma estándar de convertir a una cadena.Mal soporte para i18n
La salida basada en Iostream divide los literales de cadena en pedazos.
Las cadenas de formato ponen oraciones enteras en literales de cadena.
El último enfoque es más fácil de adaptar a las 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.fuente
$
especificadores POSIXprintf
.Estoy publicando esto como una respuesta separada porque es pura opinión.
Realizar entrada y salida (particularmente entrada) es un problema muy, muy difícil, por lo que no es sorprendente que la biblioteca iostreams esté llena de bultos y cosas que, en retrospectiva perfecta, podrían haberse hecho mejor. Pero me parece que todas las bibliotecas de E / S, en cualquier idioma, son así. Nunca he usado un lenguaje de programación en el que el sistema de E / S fuera algo bello que me hizo admirar a su diseñador. La biblioteca iostreams tiene ventajas, particularmente sobre la biblioteca CI / O (extensibilidad, seguridad de tipo, etc.), pero no creo que nadie la esté sosteniendo como un ejemplo de gran OO o diseño genérico.
fuente
Mi opinión sobre los iostreams de C ++ ha mejorado sustancialmente con el tiempo, particularmente después de que comencé a extenderlos implementando mis propias clases de stream. Comencé a apreciar la extensibilidad y el diseño general, a pesar de los nombres de funciones miembro ridículamente pobres como
xsputn
o lo que sea. De todos modos, creo que las transmisiones de E / S son una mejora masiva sobre C stdio.h, que no tiene ningún tipo de seguridad y está plagado de importantes fallas de seguridad.Creo que el principal problema con las secuencias de E / S es que combinan dos conceptos relacionados pero algo ortogonales: formato de texto y serialización. Por un lado, los flujos de E / S están diseñados para producir una representación textual formateada de un objeto, legible por humanos, 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 algunas incongruencias muy molestas. Por ejemplo:
Aquí, lo que obtenemos 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 secuencia hasta que encuentre un carácter de espacio en blanco, ya que no hay información de longitud almacenada en la secuencia. Entonces, aunque generamos un objeto de cadena que contiene "hola mundo", solo vamos a ingresar un objeto de cadena que contenga "hola". Por lo tanto, aunque la secuencia ha cumplido su propósito como una función de formateo, no ha podido serializar correctamente y luego deserializar el objeto.Se podría decir que las secuencias IO no fueron diseñadas para ser instalaciones de serialización, pero si ese es el caso, ¿para qué son realmente las secuencias de entrada ? Además, en la práctica, las secuencias de E / S a menudo se usan para serializar objetos, porque no hay otras instalaciones de serialización estándar. Considere
boost::date_time
oboost::numeric::ublas::matrix
, si saca un objeto de matriz con el<<
operador, obtendrá la misma matriz exacta cuando la ingrese usando el>>
operador. Pero para lograr esto, los diseñadores de Boost tuvieron que almacenar información de conteo de columnas y conteo de filas como datos textuales en la salida, lo que compromete la visualización real legible por humanos. Nuevamente, una combinación incómoda de facilidades de formato de texto y serialización.Observe 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
toString()
método, mientras que la serialización se logra a través de laSerializable
interfaz.En mi opinión, la mejor solución hubiera sido introducir secuencias basadas en bytes , junto con las secuencias basadas en caracteres estándar . Estas transmisiones operarían con datos binarios, sin preocuparse por el formato / visualización legible por humanos. Podrían usarse únicamente como servicios de serialización / deserialización, para traducir objetos C ++ en secuencias de bytes portátiles.
fuente
std::char_traits
que no se puede especializar de forma portátil para tomar unaunsigned char
. Sin embargo, existen soluciones alternativas, así que supongo que la extensibilidad viene al rescate una vez más. Pero creo que el hecho de que las secuencias basadas en bytes no sean estándar es una debilidad de la biblioteca.std::streambuf
. Entonces, básicamente, lo único que estás ampliando es lastd::basic_ios
clase. Entonces, hay una línea donde la "extensión" se cruza en territorio "completamente reimplementado", y la creación de un flujo binario desde las instalaciones de flujo de E / S de C ++ parece acercarse a ese punto.Siempre encontré C ++ IOStreams mal diseñado: su implementación hace que sea muy difícil definir correctamente un nuevo tipo de flujo. también mezclan características io y características de formato (piense en manipuladores).
personalmente, el mejor diseño e implementación de transmisión que he encontrado radica en el lenguaje de programación Ada. Es un modelo de desacoplamiento, un placer crear nuevos tipos de flujos, y las funciones de salida siempre funcionan independientemente del flujo utilizado. Esto es gracias a un mínimo común denominador: envía bytes a una secuencia y eso es todo. las funciones de flujo se encargan de colocar los bytes en el flujo, no es su trabajo, por ejemplo, formatear un número entero en hexadecimal (por supuesto, hay un conjunto de atributos de tipo, equivalente a un miembro de la clase, definido para manejar el formato)
Desearía que C ++ fuera tan simple con respecto a las transmisiones ...
fuente
Creo que el diseño de IOStreams es brillante en términos de capacidad de ampliación y utilidad.
Integración de localización e integración de formato. Vea lo que se puede hacer:
Puede imprimir: "cien" o incluso:
Puede imprimir "Bonjour" o "בוקר טוב" de acuerdo con la configuración regional imbuida
std::cout
!Tales cosas se pueden hacer solo porque los iostreams son muy flexibles.
¿Se podría hacer mejor?
¡Por supuesto que podría! De hecho, hay muchas cosas que podrían mejorarse ...
Hoy en día es bastante doloroso derivarlo correctamente
stream_buffer
, no es nada trivial agregar información de formato adicional para transmitir, 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 estar a punto de traer muchas cosas.
Porque no siempre puedes ver el panorama general, pero si dejas puntos para las extensiones, te da habilidades mucho mejores incluso en puntos en los que no pensaste.
fuente
print (spellout(100));
, yprint (translate("Good morning"));
esto parece como una buena idea, ya que el formato Esto desacopla y i18n de E / S.french_output << translate("Good morning")
;english_output << translate("Good morning")
te daría: "Bonjour Buenos días"out << format("text {1}") % value
y puede ser traducido a"{1} translated"
. Entonces funciona bien;-)
.(Esta respuesta se basa solo en mi opinión)
Creo que los IOStreams son mucho más complejos que sus equivalentes de funciones. Cuando escribo en C ++, sigo usando los encabezados cstdio para E / S "a la antigua", lo cual me parece mucho más predecible. En una nota al margen, (aunque no es realmente importante; la diferencia horaria absoluta es insignificante) Se ha comprobado que los IOStreams en muchas ocasiones son más lentos que CI / O.
fuente
sstringstream
. Creo que la velocidad sí importa, aunque es secundaria.Siempre me encuentro con sorpresas cuando uso el IOStream.
La biblioteca parece orientada al texto y no a los binarios. Esa puede ser la primera sorpresa: usar el indicador binario en secuencias de archivos no es suficiente para obtener un comportamiento binario. El usuario Charles Salvia anterior lo ha observado correctamente: IOStreams mezcla aspectos de formato (donde desea una salida bonita, por ejemplo, dígitos limitados para flotantes) con aspectos de serialización (donde no desea pérdida de información). Probablemente sería bueno separar estos aspectos. Boost.Serialization hace esta mitad. Tiene una función de serialización que enruta a los insertadores y extractores si lo desea. Ahí ya tienes la tensión entre ambos aspectos.
Muchas funciones también tienen una semántica confusa (p. Ej., Get, getline, ignorar y leer. Algunas extraen el delimitador, otras no; también algunas establecen eof). Además, algunos mencionan los nombres de funciones extrañas al implementar una secuencia (por ejemplo, xsputn, uflow, underflow). Las cosas empeoran aún más cuando uno usa las variantes wchar_t. El wifstream hace una traducción a multibyte mientras que wstringstream no. La E / S binaria no funciona con wchar_t: tiene que sobrescribir el codecvt.
La E / S con búfer c (es decir, ARCHIVO) no es tan potente como su contraparte de C ++, pero es más transparente y tiene un comportamiento mucho menos intuitivo.
Aún así, cada vez que me tropiezo con el IOStream, me atrae como una polilla al fuego. Probablemente sería bueno que un tipo realmente inteligente echara un buen vistazo a la arquitectura general.
fuente
No puedo evitar responder la primera parte de la pregunta (¿Quién hizo eso?). Pero fue respondido en otras publicaciones.
En cuanto a la segunda parte de la pregunta (¿Bien diseñado?), Mi respuesta es un rotundo "¡No!". Aquí un pequeño ejemplo que me hace sacudir la cabeza con incredulidad desde hace años:
El código anterior produce tonterías debido al diseño de iostream. Por algunas razones más allá de mi alcance, tratan los bytes uint8_t como caracteres, mientras que los tipos integrales más grandes se tratan como números. Qed Bad design.
Tampoco se me ocurre ninguna manera de arreglar esto. El tipo también podría ser un flotador o un doble ... así que un reparto a 'int' para hacer que 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, quizás algunas palabras más de explicación ... El diseño de 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, no como un número de byte). Este es un defecto del diseño de IOStream, ya que intentan lograr lo inalcanzable.
C ++ no permite clasificar un tipo: el lenguaje 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 suposición ES un defecto de diseño de una biblioteca.
Admitido, printf () tampoco funcionaría en una implementación genérica "ShowVector ()". Pero eso no es excusa para el comportamiento iostream. Pero es muy probable que en el caso de printf (), ShowVector () se defina así:
fuente
uint8_t
es un typedef . ¿Es realmente un char? Entonces no culpes a los iostreams por tratarlo como un char.num_put
faceta en lugar del operador de inserción de flujo.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 variable sea sencilla para principiantes. En otros lenguajes, la entrada del usuario tiende a involucrar coerción de tipo o formateadores de cadenas, mientras que C ++ hace que el compilador haga todo el trabajo. Lo mismo es cierto en gran medida 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 las clases y los conceptos orientados a objetos, lo cual es útil desde el punto de vista pedagógico, y sin tener que entender la sintaxis de formato. Nuevamente, si estás enseñando a principiantes, es una gran ventaja.
Esta simplicidad para principiantes tiene un precio, lo que puede hacer que sea un dolor de cabeza para tratar con E / S en situaciones más complejas, pero es de esperar que en ese momento el programador haya aprendido lo suficiente como para poder lidiar con ellos, o al menos haya cumplido la edad suficiente beber.
fuente