¿Quién diseñó / diseñó los IOStreams de C ++ y todavía se consideraría bien diseñado por los estándares actuales? [cerrado]

127

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).

stakx - ya no contribuye
fuente
77
La opinión de Herb Sutter es interesante stackoverflow.com/questions/2485963/… :) Lástima que ese tipo dejó SO después de solo unos días de participación
Johannes Schaub - litb
55
¿Hay alguien más que vea una mezcla de preocupaciones en las transmisiones STL? Una secuencia normalmente está diseñada para leer o escribir bytes y nada más. Una cosa que puede leer o escribir tipos de datos específicos es un formateador (que puede pero no necesita usar una secuencia para leer / escribir los bytes formateados). Mezclar ambos en una clase hace que sea aún más complejo implementar sus propios flujos.
mmmmmmmm
44
@rsteven, hay una separación de esas preocupaciones. std::streambufes la clase base para leer y escribir bytes, y istream/ ostreames para formato de entrada y salida, tomando un puntero std::streambufcomo destino / fuente.
Johannes Schaub - litb
1
@litb: ¿Pero es posible cambiar el streambuf que utiliza la secuencia (formateador)? Entonces, ¿tal vez quiero usar el formato STL pero quiero escribir los datos a través de un flujo de flujo específico?
mmmmmmmm
2
@rstevens,ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Johannes Schaub - litb

Respuestas:

31

Varias ideas mal concebidas encontraron su camino en la norma: auto_ptr, vector<bool>, valarrayy export, 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.

Marcelo Cantos
fuente
3
Gracias por su aporte. Examinaré el comp.lang.c++.moderatedarchivo y publicaré enlaces al final de mi pregunta si encuentro algo valioso. - Además, me atrevo a estar en desacuerdo con usted sobre auto_ptr: Después de leer C ++ excepcional de Herb Sutter, parece una clase muy útil al implementar el patrón RAII.
stakx - ya no contribuye
55
@stakx: Sin embargo, está quedando obsoleto y reemplazado por unique_ptruna semántica más clara y poderosa.
UncleBens
3
@UncleBens unique_ptrrequiere una referencia de valor. Entonces en este punto auto_ptres un puntero muy poderoso.
Artyom
77
Pero auto_ptrha jodido la semántica de copia / asignación que lo convierte en un nicho para eliminar errores de referencia ...
Matthieu M.
55
@TokenMacGuy: no es un vector y no almacena bools. Lo que lo hace algo engañoso. ;)
jalf
40

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.

Quuxplusone
fuente
3
@Neil - nut ¿Cuál es tu opinión sobre el diseño? Con base en sus otras respuestas, a mucha gente le encantaría saber su opinión ...
DVK
1
@DVK Acabo de publicar mi opinión como una respuesta separada.
2
Acabo de encontrar una transcripción de una entrevista con Bjarne Stroustrup donde menciona algunos fragmentos de la historia de IOStreams: www2.research.att.com/~bs/01chinese.html (este enlace parece estar roto temporalmente en este momento, pero puedes intentarlo Caché de la página de Google)
stakx - ya no contribuye
2
Enlace actualizado: stroustrup.com/01chinese.html .
FrankHB
28

Si tuviera que juzgar según los estándares actuales de ingeniería de software (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).

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:

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 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 usarlo stringstreamcomo 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.

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

Las cadenas de formato ponen oraciones enteras en literales de cadena.

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

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.

dan04
fuente
44
En realidad, para i18n, los reemplazos deben identificarse por posiciones (% 1,% 2, ..), ya que una traducción puede requerir cambiar el orden de los parámetros. De lo contrario, estoy totalmente de acuerdo - +1.
peterchen
44
@peterchen: Para eso están los $especificadores POSIX printf.
jamesdlin
2
El problema no son las cadenas de formato, es que C ++ tiene varargs no seguros para escribir.
dan04
55
A partir de C ++ 11 ahora tiene varargs de tipo seguro.
Mooing Duck
2
En mi humilde opinión, la "información adicional del estado" es el peor problema. cout es un global; adjuntar banderas de formato hace que esas banderas sean globales, y cuando considera que la mayoría de los usos tienen un alcance previsto de algunas líneas, eso es bastante horrible. Habría sido posible arreglar eso con una clase 'formateador', que se une a una corriente de entrada pero mantiene su propio estado. Y, las cosas que se hacen con cout generalmente se ven terribles en comparación con lo mismo que se hace con printf (cuando eso es posible) ..
Greggo
17

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
16

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 xsputno 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:

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 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_timeo boost::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 la Serializableinterfaz.

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.

Charles Salvia
fuente
gracias por responder. Bien podría estar equivocado sobre esto, pero con respecto a su último punto (secuencias basadas en bytes versus secuencias basadas en caracteres), ¿no es la respuesta de IOStream (parcial?) A esto la separación entre las memorias intermedias de secuencias (conversión de caracteres, transporte y almacenamiento en búfer) y secuencias (formateo / análisis)? ¿Y no podría crear nuevas clases de transmisión, las que están destinadas exclusivamente a la serialización y deserialización (legible por máquina) y otras que están orientadas exclusivamente al formato y análisis (legible por humanos)?
stakx - ya no contribuye el
@stakx, sí, y de hecho, he hecho esto. Es un poco más molesto de lo que parece, ya std::char_traitsque no se puede especializar de forma portátil para tomar una unsigned 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.
Charles Salvia
44
Además, la implementación de secuencias binarias requiere que implemente nuevas clases de secuencia y nuevas clases de búfer, ya que las preocupaciones de formato no están completamente separadas std::streambuf. Entonces, básicamente, lo único que estás ampliando es la std::basic_iosclase. 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.
Charles Salvia
bien dicho y exactamente lo que sospechaba. Y el hecho de que tanto C como C ++ hagan grandes esfuerzos para no garantizar las anchuras de bits y las representaciones específicas puede ser problemático cuando se trata de hacer E / S.
stakx - ya no contribuye el
" para serializar un objeto en un formato portátil " . No, nunca tuvieron la intención de soportar eso
curioso
11

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 ...

Adrien Plisson
fuente
El libro que mencioné explica la arquitectura básica de IOStreams de la siguiente manera: hay una capa de transporte (las clases de búfer de flujo) y una capa de análisis / formateo (las clases de flujo). Los primeros son responsables de leer / escribir caracteres de / a un bytestream, mientras que los segundos son responsables de analizar caracteres o serializar valores en caracteres. Esto parece bastante claro, pero no estoy seguro de si estas preocupaciones están realmente claramente separadas en realidad, especialmente. cuando los locales entran en juego. - También estoy de acuerdo con usted en la dificultad de implementar nuevas clases de transmisiones.
stakx - ya no contribuye
"mezclar funciones io y funciones de formateo" <- ¿Qué hay de malo en eso? Ese es el punto de la biblioteca. Con respecto a la creación de nuevas transmisiones, debe realizar un flujo continuo en lugar de una secuencia y construir una secuencia simple alrededor del flujo continuo.
Billy ONeal
Parece que las respuestas a esta pregunta me hicieron entender algo que nunca me explicaron: debería derivar un flujo de agua en lugar de una corriente ...
Adrien Plisson
@stakx: Si la capa streambuf hiciera lo que dijiste, estaría bien. Pero la conversión entre la secuencia de caracteres y el byte se mezclan con la E / S real (archivo, consola, etc.). No hay forma de realizar la E / S del archivo sin también realizar la conversión de caracteres, lo cual es muy desafortunado.
Ben Voigt
10

Creo que el diseño de IOStreams es brillante en términos de capacidad de ampliación y utilidad.

  1. Búferes de transmisión: eche un vistazo a las extensiones boost.iostream: cree gzip, tee, copie transmisiones en pocas líneas, cree filtros especiales, etc. No sería posible sin él.
  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 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.

Artyom
fuente
55
¿Puede proporcionar un comentario sobre por qué sus ejemplos para el punto 2 sería mejor que el simple uso de algo así print (spellout(100));, y print (translate("Good morning"));esto parece como una buena idea, ya que el formato Esto desacopla y i18n de E / S.
Programado
3
Porque se puede traducir según el idioma imbuido en la secuencia. es decir: french_output << translate("Good morning"); english_output << translate("Good morning") te daría: "Bonjour Buenos días"
Artyom
3
La localización es mucho más difícil cuando necesita hacer '<< "texto" << valor' en un idioma pero '<< valor << "texto"' en otro - comparado con printf
Martin Beckett
@ Martin Beckett Lo sé, eche un vistazo a la biblioteca Boost.Locale, lo que sucede que en ese caso lo hace out << format("text {1}") % valuey puede ser traducido a "{1} translated". Entonces funciona bien ;-).
Artyom
15
Lo que "se puede hacer" no es muy relevante. Eres un programador, cualquier cosa se puede hacer con suficiente esfuerzo. Pero IOStreams hace que sea terriblemente doloroso lograr la mayor parte de lo que se puede hacer . Y generalmente obtienes un rendimiento pésimo por tus problemas.
jalf
2

(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.

Delan Azabani
fuente
Creo que te refieres a "función" en lugar de "funcional". La programación funcional produce código que es aún peor si se mira que la programación genérica.
Chris Becke,
Gracias por señalar ese error; He editado la respuesta para reflejar la corrección.
Delan Azabani
55
IOStreams seguramente tendría que ser más lento que el clásico stdio; si me dieran la tarea de diseñar un marco de flujos de E / S extensible y fácil de usar, probablemente juzgaría la velocidad secundaria, dado que los cuellos de botella reales probablemente serán la velocidad de E / S de archivo o el ancho de banda del tráfico de red.
stakx - ya no contribuye
1
Estoy de acuerdo en que para E / S o red, la velocidad computacional no importa mucho. Sin embargo, recuerde que está utilizando C ++ para la conversión numérica / de cadenas sstringstream. Creo que la velocidad sí importa, aunque es secundaria.
Matthieu M.
1
La E / S de archivos @stakx y los cuellos de botella de la red son una función de los costos 'por byte', que son bastante pequeños y se ven reducidos drásticamente por las mejoras tecnológicas. Además, dado DMA, estos gastos generales no quitan el tiempo de CPU de otros subprocesos en la misma máquina. Por lo tanto, si está realizando una salida formateada, el costo de hacerlo de manera eficiente versus no, puede ser fácilmente significativo (al menos, no eclipsado por el disco o la red; lo más probable es que se vea eclipsado por otro procesamiento en la aplicación).
Greggo
2

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.

gast128
fuente
1

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:

#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 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í:

template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );
BitTickler
fuente
3
La culpa no radica (puramente) en iostream. Compruebe para qué uint8_tes un typedef . ¿Es realmente un char? Entonces no culpes a los iostreams por tratarlo como un char.
Martin Ba
Y si desea asegurarse de obtener un número en código genérico, puede usar la num_putfaceta en lugar del operador de inserción de flujo.
Martin Ba
@ Martin Ba Tiene razón: los estándares de c / c ++ mantienen abierto cuántos bytes tiene un "int sin signo corto". "unsigned char" es una idiosincrasia del lenguaje. Si realmente quieres un byte, debes usar un carácter sin signo. C ++ tampoco permite imponer restricciones a los argumentos de plantilla, como "solo números" y, por lo tanto, si cambié la implementación de ShowVector a su solución num_put propuesta, ShowVector ya no podría mostrar un vector de cadenas, ¿verdad? ;)
BitTickler
1
@ Martin Bla: cppreference menciona que int8_t es un tipo de entero con signo con un ancho de exactamente 8 bits. Estoy de acuerdo con el autor en que es extraño que obtenga salida de basura entonces, aunque es técnicamente explicable por el typedef y la sobrecarga de tipos de caracteres en iostream . Podría haberse resuelto teniendo un __int8 como un tipo verdadero en lugar de un typedef.
gast128
Oh, en realidad es bastante fácil de arreglar: // Corrige std :: ostream que ha roto el soporte para tipos sin firmar / firmados / char // e imprime enteros de 8 bits como si fueran caracteres. espacio de nombres ostream_fixes {inline std :: ostream & operator << (std :: ostream & os, unsigned char i) {return os << static_cast <unsigned int> (i); } std en línea :: ostream y operador << (std :: ostream y os, firmado char i) {return os << static_cast <firmado int> (i); }} // namespace ostream_fixes
mcv
1

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.

usuario2310967
fuente