Las buenas prácticas de diseño dictan que, cuando cree una nueva clase, debe esforzarse en ocultar los detalles de la implementación subyacente tanto como sea posible al usuario de la clase. Usted le muestra solo aquello que necesita conocer y el resto se hace privado para evitar confusiones. Cuando usamos insertadores y extractores, normalmente usted no conoce o tiene cuidado con los bytes que se consumen o se producen, ya que usted está tratando con E/S estándar, ficheros, memoria, o alguna nueva clase o dispositivo creado.
Llega un momento, no obstante, en el que es importante comunicar
con la parte del iostream
que produce o
consume bytes. Para proveer esta parte con una interfaz común y
esconder todavía su implementación subyacente, la librería
estándar la abstrae dentro de su clase, llamada
streambuf
. Cada objeto
iostream
contiene un puntero a alguna
clase de streambuf
. (El tipo depende de
que se esté tratando con E/S estándar, ficheros, memoria,
etc.). Puede acceder al streambuf
directamente; por ejemplo, puede mover bytes sin formatear
dentro y fuera del streambuf
sin
formatearlos a través de la encapsulación del
iostream
. Esto es posible llamando a las
funciones miembro del objeto streambuf
.
Actualmente, la cosa más importante que debe conocer es que cada
objeto iostream
contiene un puntero a un
objeto streambuf
, y el objeto
streambuf
tiene algunas funciones miembro
que puede llamar si es necesario. Para ficheros y
streams
de string, hay tipos
especializados de buffers de stream
, como
ilustra la figura siguiente:
Para permitirle el acceso al streambuf
, cada objeto iostream
tiene una
función miembro llamada rdbuf()
que retorna el puntero a un objeto streambuf
.
De esta manera usted puede llamar cualquier función miembro del streambuf
subyacente. No obstante,
una de las cosas más interesantes que usted puede hacer con el puntero al streambuf
es conectarlo
con otro objeto iostream
usando el operador <<. Esto inserta todos los carácteres del objeto
dentro del que está al lado izquierdo del <<. Si quiere mover todos los carácteres de un iostream
a otro, no necesita ponerse con el tedioso (y potencialmente inclinado a errores de código) proceso de leer de carácter
por carácter o línea por línea. Este es un acercamiento mucho más elegante.
Aqui está un programa muy simple que abre un fichero y manda el contenido a la salida estándar (similar al ejemplo previo):
//: C04:Stype.cpp // Type a file to standard output. #include <fstream> #include <iostream> #include "../require.h" using namespace std; int main() { ifstream in("Stype.cpp"); assure(in, "Stype.cpp"); cout << in.rdbuf(); // Outputs entire file } ///:~
Listado 5.6. C04/Stype.cpp
Un ifstream se crea usando el fichero de código fuente para este programa como argumento. La función
assure()
reporta un fallo si el fichero no puede ser abierto. Todo el trabajo pasa
realmente en la sentencia
cout << in.rdbuf();
que manda todo el contenido del fichero a cout
. No solo es un código más sucinto,
a menudo es más eficiente que mover los byte de uno en uno.
Una forma de get()
escribe directamente dentro del streambuf
de otro objeto. El primer argumento es una referencia al streambuf
de destino, y el
segundo es el carácter de terminación ('\n' por defecto), que detiene la función get()
.
Así que existe todavía otra manera de imprimir el resultado de un fichero en la salida estándar:
//: C04:Sbufget.cpp // Copies a file to standard output. #include <fstream> #include <iostream> #include "../require.h" using namespace std; int main() { ifstream in("Sbufget.cpp"); assure(in); streambuf& sb = *cout.rdbuf(); while(!in.get(sb).eof()) { if(in.fail()) // Found blank line in.clear(); cout << char(in.get()); // Process '\n' } } ///:~
Listado 5.7. C04/Sbufget.cpp
La función rdbuf()
retorna un puntero, que tiene que ser desreferenciado para satisfacer las
necesidades de la función para ver el objeto. Los buffers de stream
no estan pensados para
ser copiados (no tienen contructor de copia), por lo que definimos sb
como una referencia al buffer
de stream
de cout
. Necesitamos las llamadas a fail()
y clear()
en caso de que el fichero de entrada tenga una línea en blanco (este la tiene). Cuando
esta particular versión sobrecargada de get()
vee dos carácteres de nueva línea en una fila (una
evidencia de una línea en blanco), activa el bit de error del stream
de entrada, asi que se debe
llamar a clear()
para resetearlo y que así el stream
pueda continuar siendo
leído. La segunda llamada a get()
extrae y hace eco de cualquier delimitador de nueva línea. (Recuerde,
la función get()
no extrae este delimitador como sí lo hace getline()
).
Probablemente no necesitará usar una técnica como esta a menudo, pero es bueno saber que existe.[15]
[15] Un tratado mucho más en profundidad de buffers de stream y streams en general puede ser encontrado en[ Langer & Kreft's, Standar C++ iostreams and Locales, Addison-Wesley, 1999.]