5.3.2f basic_streambuf
§1 Sinopsis
Las operaciones E/S C++ están organizadas en dos capas: de formato y de transporte ( 5.3). La capa de transporte es la encargada de la conexión con el dispositivo (fichero) externo; del transporte de la secuencia de caracteres, y de la conversión de código necesaria para adecuar el formato interno con el del dispositivo externo. Un elemento fundamental en el mecanismo de transporte es un almacenamiento interno que denominamos buffer interno; buffer de flujo.
Esta capa está representada por objetos streambuf, o wstreambuf si son de caracteres anchos, derivados de una jerarquía cuya raíz basic_streambuf, es una superclase abstracta ( 5.3.2) de la que se derivan clases específicas para controlar distintos dispositivos físicos.
Nota: debido a lo anterior, en los ejemplos que siguen se utilizan objetos de clases derivadas
Según muestra la figura adjunta, la Librería Estándar define dos subclases, cada una de las cuales existe en dos versiones (caracteres normales y anchos). La primera destinada a flujos de memoria (en los que el buffer interno se confunde con el dispositivo externo). La segunda, destinada a E/S de fichero ("named files").
§2 El buffer interno
El buffer de interno dispone de dos áreas, una para caracteres de entrada ("get area") y otra para los de salida ("put area"). Estas áreas se concretan en dos matrices de caracteres y en cada una existen tres punteros de control [1]: uno al principio; otro a la posición después de la actual (candidata a la próxima operación de E/S) y el tercero a una posición después del final; se denominan xbeg, xnext y xend respectivamente.
Los métodos que extraen e insertan caracteres en el flujo utilizan estos punteros para su funcionamiento. Por ejemplo, sbumpc() devuelve el carácter señalado por el puntero actual en el área de entrada e incrementa la el valor del puntero correspondiente en una unidad. Por contra, el método sgetc() devuelve el carácter señalado por el puntero actual sin alterarlo. Similarmente sputc() inserta un carácter en la posición actual del área de salida, e incrementa el puntero correspondiente.
El área de entrada representa al dispositivo de entrada y generalmente contiene una fracción de los datos de aquel. El buffer de salida representa los datos internos y también suele representar una fracción de los mismos. Ambos almacenamientos representan en todo momento una ventana sobre la totalidad de datos a transportar (los contenidos en el dispositivo externo y los internos).
Los objetos streambuf pueden imponer restricciones diversas a las secuencias que controlan. Entre otras:
Que la secuencia de entrada no puede ser leída.
Que la secuencia de salida no pueda ser escrita. Por ejemplo, apertura de "ficheros" en forma solo-lectura.
La secuencia puede ser asociada con otra representación externa de una secuencia de caracteres. Por ejemplo, un fichero externo.
La secuencia puede soportar operaciones directas hacia/desde otras secuencias asociadas ( 5.3.2c).
La secuencia puede imponer limitaciones relativas a la forma en que pueden ser leídos sus caracteres, a como pueden ser escritos, devolver caracteres a la secuencia de entrada ("putback"), o alterar la posición del puntero actual dentro de la secuencia ("seek pos").
§3 Interfaz
template <class charT, class traits = char_traits<charT> >
class basic_streambuf {
public:
// Tipos:
typedef charT char_type;
typedef typename traits::int_type int_type;
typedef typename traits::pos_type pos_type;
typedef typename traits::off_type off_type;
typedef traits traits_type;
// Localismos:
locale pubimbue(const locale & loc);
locale getloc() const;
// Control de buffer y posicionamiento:
basic_streambuf<char_type,traits>*
pubsetbuf(char_type* s, streamsize n);
pos_type pubseekoff(off_type off, ios_base::seekdir way,
ios_base::openmode which = ios_base::in | ios_base::out);
pos_type pubseekpos(pos_type sp,
ios_base::openmode which = ios_base::in | ios_base::out);
int pubsync();
// Área de entrada:
streamsize in_avail();
int_type snextc();
int_type sbumpc();
int_type sgetc();
streamsize sgetn(char_type* s, streamsize n);
// Devolución de caracteres:
int_type sputbackc(char_type c);
int_type sungetc();
// Área de salida:
int_type sputc(char_type c);
streamsize sputn(const char_type* s, streamsize n);
virtual ~basic_streambuf(); // Destructor (público-virtual)
protected:
// Miembros protegidos
basic_streambuf();
// Constructor por defecto
// Área de entrada:
char_type* eback() const;
char_type* gptr() const;
char_type* egptr() const;
void gbump(int n);
void setg(char_type* gbeg, char_type* gnext, char_type* gend);
// Área de salida:
char_type* pbase() const;
char_type* pptr() const;
char_type* epptr() const;
void pbump(int n);
void setp(char_type* pbeg, char_type* pend);
virtual void imbue(const locale & loc);
// localismo
// Control de buffer y posicionamiento:
virtual basic_streambuf<char_type,traits>*
setbuf(char_type* s, streamsize n);
virtual pos_type seekoff(off_type off, ios_base::seekdir way,
ios_base::openmode which = ios_base::in | ios_base::out);
virtual pos_type seekpos(pos_type sp,
ios_base::openmode which = ios_base::in | ios_base::out);
virtual int sync();
// Área de entrada:
virtual int showmanyc();
virtual streamsize xsgetn(char_type* s, streamsize n);
virtual int_type underflow();
virtual int_type uflow();
virtual int_type pbackfail(int_type c = traits::eof());
// Área de salida:
virtual streamsize xsputn(const char_type* s, streamsize n);
virtual int_type overflow(int_type c = traits::eof());
};
§3.1 Comentario
La clase es un verdadero muestrario; dispone de miembros públicos y protegidos. En cuanto a sus métodos, existen los dos tipos: virtuales y no-virtuales. De los primeros, empezando por el propio destructor, existe un buen surtido y que se refieren a aspectos concretos del dispositivo externo que deben ser implementados en las clases derivadas.
El grupo de miembros protegidos, encabezado por el constructor por defecto, solo pueden ser accedidos por las clases derivadas. Dado que estos métodos no son accedidos directamente, sino a través de métodos de otras clases, serán omitidos en la explicación pormenorizada. El hecho de ser protegido su constructor, garantiza que solo se podrán instanciar objetos basic_streambuf desde sus clases derivadas.
Otro grupo lo componen los miembros públicos que representan las funcionalidades genéricas ofrecidas por la superclase, cuyos detalles deberán ser implementados en las clases derivadas.
§4 Sincronización
Para comprender el sentido y mecánica de funcionamiento de algunos métodos de la jerarquía iostream, es importante conocer los procesos de sincronización, que ocurren en las operaciones de transporte.
Como se ha señalado, estas operaciones son controladas por el objeto streambuf, que representa al buffer intermedio, y se efectúan de forma automática. El buffer es rellenado ("refilled") con nuevos datos cuando se precisa, y vaciado ("flushed") cuando es necesario. Además de esta sincronización implícita, la sincronización puede ser forzada explícitamente de varias formas:
Porque el flujo y, en consecuencia, el streambuf sea destruido.
Porque la conexión entre el flujo y el dispositivo externo asociado, sea cerrada.
Porque se utilicen explícitamente los métodos flush ( 5.3.2d) para flujos de salida, o sync( 5.3.2c) para flujos de entrada (en ambos casos, estos métodos delegan en el método pubsync() de basic_streambuf, que delega a su vez en un método privado virtual sync().
-
Porque se utilice el indicador unitbuf de la máscara de formato fmtflags de ios_base ( 5.3.2a).
-
Porque se enlacen dos flujos mediante el método tie ( 5.3.2b) de basic_ios.
§5 Localismos
Se dispone de dos métodos para controlar el locale asociado al buffer cuya sintaxis y funcionamiento son exactamente simétricos a los de igual nombre en ios_base ( 5.3.2a). La diferencia es que aquellos se aplican sobre flujos y estos se aplican sobre un streambuf.
§5.1 pubimbue()
locale pubimbue(const locale & loc);
Este método devuelve el localismo actual (antes de la invocación). A continuación imbuye en el buffer el localismo pasado como argumento.
Nota: en realidad la operación se realiza mediante la invocación del método protegido virtual void imbue(const locale & loc); que depende del dispositivo concreto conectado al buffer.
§5.2 getloc()
locale getloc() const;
En el momento de la creación de un objeto streambuf, se asocia automáticamente el localismo global, de forma que este método devuelve el locale global si no se ha utilizado antes el método pubimbue(). En otro caso devuelve el localismo actualmente imbuido en el buffer. Ejemplo:
std::stringbuf string_buff;
std::locale loc = string_buff.getloc();
std::cout << "Locale: " << loc.name() << std::endl; // -> Locale: C
§6 Control de buffer y posicionamiento
En este grupo hay métodos públicos y protegidos (que son además virtuales). Generalmente los miembros públicos realizan su tarea mediante la invocación de un método protegido cuya forma concreta depende del dispositivo físico controlado por el buffer. Así pues, la forma concreta de estas funciones varía en cada subclase de basic_streambuf.
§6.1 pubsetbuf()
basic_streambuf<char_type,traits>* pubsetbuf(char_type* s, streamsize n);
Este método invoca la función protegida
virtual basic_streambuf<char_type,traits>* setbuf(char_type* s, streamsize n);
La idea es que el usuario pueda proporcionar su propio buffer o redimensionar el actual.
§6.2 pubseekoff()
pos_type pubseekoff(off_type off, ios_base::seekdir way,
ios_base::openmode which
= ios_base::in | ios_base::out);
El método invoca a la función protegida
virtual pos_type seekoff(off_type off, ios_base::seekdir way,
ios_base::openmode which = ios_base::in | ios_base::out);
y devuelve el resultado de la invocación. La idea es alterar la posición de un flujo de forma que pueda ser definida separadamente para cada subclase derivada de basic_streambuf. El comportamiento por defecto es devolver un objeto de tipo pos_type que contiene una posición no válida en el flujo.
Nota: pos_type es un alias de traits::pos_type .
§6.3 pubseekpos()
pos_type pubseekpos(pos_type sp,
ios_base::openmode which
= ios_base::in | ios_base::out);
Este método invoca a la función protegida
virtual pos_type seekpos(pos_type sp,
ios_base::openmode which = ios_base::in | ios_base::out);
y devuelve el resultado de la invocación. El objeto es alterar la posición en la secuencia (fichero/dispositivo) asociado. El detalle se concreta en cada subclase específica. Por ejemplo, en basic_filebuf el funcionamiento de la invocación de este método es el siguiente: altera la posición del fichero asociado al buffer en la medida señalada por sp. El argumento which, de tipo ios_base::openmode ( 5.3.2a), afecta la operación en la siguiente forma:
Condición | Efecto |
(which & basic_ios::in) != 0 | Establece la posición en sp. A continuación actualiza la secuencia de entrada. |
(which & basic_ios::out) != 0 | Establece la posición en sp. A continuación actualiza la secuencia de salida. |
Cualquier otro caso | La operación fracasa |
Si sp es una posición inválida, la operación fracasa. En caso de éxito la función devuelve sp. Si sp no ha sido obtenida previamente mediante la invocación a un método válido de posicionamiento, el resultado es indefinido.
§6.4 int pubsync();
Este método invoca la función protegida virtual int sync();. Esta última sincroniza la secuencia con el buffer en una forma que puede ser particularizada en cada subclase derivada de basic_streambuf. El sentido último es sincronizar el flujo con el dispositivo externo.
El comportamiento por defecto es no hacer nada y devolver 0. En caso de fallo el valor devuelto es -1.
§7 Control área de entrada
Existen varios métodos para controlar el área de entrada ("get area"). Como en los demás casos, hay miembros públicos y protegidos (estos últimos son virtuales en muchos casos). Generalmente los miembros públicos se apoyan en los protegidos para hacer su trabajo.
§7.1 in_avail()
streamsize in_avail();
Si la posición de lectura es válida, devuelve egptr( ) - gptr(). En caso conctrario, devuelve showmanyc( ).
§7.2 snextc()
int_type snextc();
Este método invoca a sbumpc(). Si esta función devuelve traits::eof(), devuelve este valor. En otro caso devuelve sgetc ().
El comportamiento puede ser expresado en otras palabras: si existe un carácter en el área de entrada, devuelve su valor y avanza el puntero xnext correspondiente. En caso que se haya alcanzado el fin de fichero, se devuelve este valor (EOF).
§7.3 sbumpc()
int_type sbumpc();
Si hay un carácter disponible en el área de entrada, devuelve traits::to_int_type(*gptr( )) e incrementa en una unidad el puntero xnext de dicha área. En caso contrario devuelve el resultado de la invocación al método uflow().
§7.4 sgetc()
int_type sgetc();
Si existe un carácter disponible en el área de entrada, devuelve traits::to_int_type(*gptr( )). En caso contrario devuelve el resultado de la invocación al método protegido underflow(). Como puede verse, este método no incrementa el puntero de lectura xnext, que queda como estaba. Ejemplo:
std::stringbuf string_buff;
std::iostream io_stream (&string_buff);
io_stream << "0123456789-End";
char c = string_buff.sgetc();
while ( c != EOF) {
std::cout << c;
c = string_buff.snextc();
}
std::cout << std::endl; // -> 0123456789-End
§7.5 sgetn()
streamsize sgetn(char_type* s, streamsize n);
Este método devuelve el resultado de la invocación al método protegido xsgetn( s, n).
§7.a xsgetn()
virtual streamsize xsgetn(char_type* s, streamsize n); // Protegido
Asigna sucesivamente caracteres del área de entrada a la matriz cuyo primer miembro está señalado por el argumento s. Su acción equivale a la invocación repetida del método sbumpc( ). La asignación termina cuando se han extraído n caracteres o una llamada a this->sbumpc() hubiese devuelto traits_type::eof(). Es decir, se hubiese alcanzado el final del fichero. El método devuelve el número de caracteres leídos.
§7.b gptr()
char_type* gptr() const; // Protegido
Este método es utilizado como parte del mecanismo de funcionamiento de otros muchos. Devuelve el valor del puntero xnext de la secuencia de entrada.
§7.c egptr()
char_type* egptr() const; // Protegido
Devuelve el puntero xend de la secuencia de entrada.
§7.d eback()
char_type* eback() const; // Protegido
Devuelve el puntero xbeg de la secuencia de entrada.
§7.e void gbump(int n); // Protegido
void gbump(int n); // Protegido
Avanza n caracteres el puntero xnext de la secuencia de entrada
§7.f uflow()
virtual int_type uflow(); // Protegido
Este método invoca underflow(). Si este último devuelve traits::eof(), devuelve también traits::eof(). En caso contrario devuelve el resultado de traits::to_int_type(*gptr()) e incrementa el valor del puntero xnext de la secuencia de entrada.
§7.g showmanyc()
virtual int showmanyc(); // Protegido [2]
Devuelve una estimación del número de caracteres disponibles en la secuencia o 1. Si devuelve un valor positivo, las invocaciones sucesivas a underflow() pueden no devolver traits::eof() hasta que al menos dicho número de caracteres hayan sido proporcionados. Si devuelve 1, entonces las invocaciones a underflow() or uflow() pueden fallar.
El comportamiento por defecto es devolver cero.
§7.h setg()
void setg(char_type* gbeg, char_type* gnext, char_type* gend); // Protegido
Este método establece los punteros xbeg, xnext y xend a los valores gbeg, gnext y gend pasados en los argumentos.
§7.i underflow()
virtual int_type underflow(); // Protegido
Este método maneja las situaciones en que el área de entrada no existe o está vacía. Por ejemplo, gptr() == 0 o gptr() >= egptr(). Se trata siempre de situaciones anómalas, por lo que el valor devuelto es siempre traits_type::eof()
§8 Control área de salida
§8.1 sputc()
int_type sputc(char_type c);
Si hay una posición disponible para escritura en el área de salida, se almacena c en dicha posición, se incrementa el puntero xnext del área correspondiente y se devuelve traits::to_int_type(c). En caso contrario se devuelve overflow( traits::to_int_type(c)).
§8.2 sputn()
streamsize sputn(const char_type* s, streamsize n);
Este método devuelve el resultado de invocar xsputn(s, n).
§8.a xsputn()
virtual streamsize xsputn(const char_type* s, streamsize n); // Protegido
Escribe n caracteres en la secuencia de salida de forma análoga a repetidas invocaciones a sputc(c) (con incremento del puntero xnext). Los caracteres a escribir se obtienen de los elementos sucesivos de una matriz cuyo primer elemento está señalado por el puntero s. La escritura finaliza después que se han escrito n caracteres o una invocación a sputc(c) devolviera traits::eof(). Es decir, se hubiese alcanzado el fin de fichero.
El resultado devuelto es el número de caracteres escritos.
§8.b pbase()
char_type* pbase() const; // Protegido
Devuelve el puntero xbeg de la secuencia de salida.
§8.c pptr()
char_type* pptr() const; // Protegido
Devuelve el puntero xnext de la secuencia de salida.
§8.d epptr()
char_type* epptr() const; // Protegido
Devuelve el puntero xend de la secuencia de salida..
§8.e overflow()
virtual int_type overflow(int_type c = traits::eof()); // Protegido
Este método maneja las situaciones en que no es posible insertar caracteres en el área de salida porque esta no existe o está llena. Por ejemplo, pptr() == 0 o pptr() >= epptr().
Debido a que se trata de situaciones anómalas, el método devuelve siempre traits_type::eof().
§9 Devolución de caracteres
Las rutinas de devolución de caracteres afectan al área de entrada y están pensadas para "deshacer" la última operación de lectura. Existen tres de estos métodos. Los dos primeros, sputbackc() y sungetc() se refieren a la devolución de un carácter a la secuencia de entrada. El tercero, pbackfail() es un manejador de situaciones especiales que, en caso necesario, recompone situaciones erróneas.
§9.1 sputbackc()
int_type sputbackc(char_type c);
Si la posición putback de la secuencia de entrada no está disponible, o si traits::eq(c, gptr()[-1]) es falso, devuelve pbackfail(traits::to_int_type(c)). En caso contrario, decrementa el puntero xnext de la secuencia de entrada y devuelve traits::to_int_type(*gptr()).
Esta esotérica descripción de la norma describe el siguiente comportamiento [3]: "Si es posible", el método inserta carácter c en la posición anterior a la señalada por el puntero xnext de la secuencia de entrada, y decrementa su valor, con lo que el carácter c aparece como candidato para la próxima operación de lectura.
La condición de "ser posible" la inserción exige: que xnext != 0, o dicho en otras palabras, que se haya extraído previamente algún carácter, y que xbeg < xnext. Podríamos describir esta última condición como que "exista área de entrada".
En caso de éxito, la función devuelve el equivalente numérico (int) del carácter señalado por xnext (que debe ser c). En caso de fallo (no ser posible), devuelve el resultado de invocar el método pbackfail(c).
§9.2 sungetc()
int_type sungetc();
Si la posición putback de la secuencia de entrada no está disponible, devuelve pbackfail(). En caso contrario decrementa el puntero xnext de la secuencia de entrada y devuelve traits::to_int_type(*gptr()).
Dicho en otras palabras: el método decrementa el puntero xnext del área de entrada, con lo que el último carácter leído vuelve a aparecer como candidato de la próxima lectura. Como en el caso anterior, esto exige que xnext != 0 (se haya leído algo anteriormente) y que xbeg < xnext. En caso de éxito se devuelve el equivalente numérico del carácter señalado por xnext (debe coincidir con el último leído). En caso contrario se devuelve el resultado de invocar pabackfail().
§9.a pbackfail()
virtual int_type pbackfail(int_type c = traits::eof()); // Protegido
La idea de esta función es ser invocada cuando no es posible la operación de las anteriores por alguna de las causas indicadas. En este caso, pbackfail recibe un valor numérico (que representa un carácter) e intenta situarlo en la posición anterior a xnext. Al tiempo que reajusta los punteros para que resulte el próximo candidato para lectura y sea posible también una ulterior operación de "putback". Es decir, la invocación sin problemas de sputbackc() o sungetc().
En caso de fallo, el método devuelve traits::eof(). Cualquier otro valor señalaría el éxito de la operación. Observe que el valor utilizado por defecto, cuando se invoca sin argumento, es justamente el valor devuelto en caso de fallo.
[1] La disposición exacta (dos áreas de almacenamientos independientes o solo una, su tamaño, Etc.) depende de la implementación. Pero cualquiera que sea esta, su mecánica de funcionamiento sigue las pautas indicadas.
[2] El documento del Estándar advierte que los morfemas de esta palabra son "es-how-many-see" y no "show-manic" como podría parecer.
[3] Debido a que el Estándar no se refiere a ninguna forma concreta de implementación, las descripciones de este tipo de "mecanismos" son perfectamente asépticas, y se limitan a describir sin ambigüedades el comportamiento de la función o método. Además intenta ser concisa y utilizar la sintaxis C++ para las descripciones. El resultado es que la norma es una perfecta guía de diseño para el que escribe una nueva librería o compilador, pero bastante oscura para el neófito que pretende iniciarse en los secretos del lenguaje.