Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


5.3.2b  basic_ios

§1  Sinopsis

basic_ios es una clase genérica (plantilla) que incluye funcionalidades genéricas requeridas por todos los iostreams.  Se encarga de iniciar aquellas propiedades de la superclase ios_base que tienen relación con el control de estado e integridad del flujo y del buffer intermedio. Uno de sus métodos sirve de nexo de unión entre los flujos y el referido almacenamiento interno ( 5.3.2f).

Entre los aspectos genéricos que le incumben, se incluye la especialización, en función del tipo de objetos que lo constituyan (caracteres normales o anchos); aunque la jerarquía puede ser extendida para controlar flujos de cualquier tipo definido por el usuario.

La especialización en función del tipo de operación, se realiza en sus clases derivadas, que son especializaciones para operaciones de entrada, de salida, o combinación de ambas (E/S).

§2 Interfaz

template <class charT, class traits = char_traits<charT> >

   class basic_ios : public ios_base {

   public:            // Miembros públicos

 

// 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;

   locale imbue(const locale& loc);        // localismo 5.3.2a

 

// Operadores auxiliares           

   operator void*() const

   bool operator!() const

 

// Control del flujo               

   iostate rdstate() const;

   void clear(iostate state = goodbit);

   void setstate(iostate state);

   bool good() const;

   bool eof() const;

   bool fail() const;

   bool bad() const;

   iostate exceptions() const;

   void exceptions(iostate except);

 

// Control del buffer interno     

   basic_ostream<charT,traits>* tie() const;

   basic_ostream<charT,traits>* tie(basic_ostream<charT,traits>* tiestr);

   basic_streambuf<charT,traits>* rdbuf() const;

   basic_streambuf<charT,traits>* rdbuf(basic_streambuf<charT,traits>* sb);

   basic_ios& copyfmt(const basic_ios& rhs);

   char_type fill() const;   char_type fill(char_type ch);

 

// Conversión de tipos                          

   char narrow(char_type c, char dfault) const;

   char_type widen(char c) const;

 

// Constructores/destructores:

   explicit basic_ios(basic_streambuf<charT,traits>* sb); 

   virtual ~basic_ios();

 

   protected:            // Miembros protegidos

   basic_ios();          // Constructor por defecto

   void init(basic_streambuf<charT,traits>* sb);

   private:              // Miembros privados

   basic_ios(const basic_ios& );            // not defined

   basic_ios& operator=(const basic_ios&);  // not defined

};

§2.1  Comentario

Como puede verse, es una clase genérica (plantilla  4.12.2) que deriva públicamente de ios_base y que depende de dos argumentos que son clases: charT, traits.

§3  Tipos

La interfaz comienza definiendo cinco tipos:

  • char_type.  Sinónimo de charT

  • int_type.  Sinónimo de traits::int_type.

  • pos_type.  Sinónimo de traits::pos_type

  • off_type.  Sinónimo de traits::off_type

  • traits_type. Sinónimo de traits.

§4  Operadores auxiliares

basic_ios utiliza un operador de conversión y su propia versión del operador de negación lógica.

   operator void*() const;

Esta declaración es en realidad un operador de conversión ( 4.9.18k) utilizado por el compilador cuando necesita transformar un iostream en otro tipo. Por ejemplo, permite construcciones como:

float s = 0, n;

while (std::cin >> n) {

   s += n;

}

std::cout >> "Suma total = " << s;

Este código permitiría introducir una serie de datos por teclado y obtener la suma, pero ocurre que los extractores de los iostreams están definidos de modo que devuelven una referencia al propio flujo ( 5.3.3b). En concreto, el prototipo del operador de extracción, del flujo estandar de entrada cin para floats, tiene el siguiente aspecto:

std::cout& operator>>(float& f);


Sabemos que la sentencia while necesita evaluar el resultado de cada extracción en términos de un valor lógico cierto/falso, o como mínimo a un valor numérico para el que disponga de una conversión estándar ( 2.2.5), sin embargo, las conversiones estándar de clases no contemplan este supuesto ( 2.2.5a). Para proporcionar la conversión adecuada, el operador de conversión que comentamos tiene una definición que responde aproximadamente al siguiente esquema [3]:

operator void* () {

   if ( resultado-de-operación-Ok )

      return this;

   else

      return 0;

}

Como puede verse, este diseño devuelve un puntero al propio flujo, o cero, según sea el resultado de la extracción. En ambos casos es perfectamente posible un "casting" a puntero-a-void. Finalmente, el tipo devuelto dispone de un mecanismo de conversión a bool, que es el tipo exigido por while. El truco aquí es que la evaluación resultado-de-operación-Ok, es realizado por el método mediante los recursos auxiliares de control de flujo que están disponibles en la propia clase. Por ejemplo:

operator void* () {   if ( fail() ) return 0;    else return this; }

Ver a continuación una descripción del método fail() en Control de flujo .

  bool operator!() const;

La clase dispone también de su propia versión sobrecargada del operador de negación lógica ! NOT ( 4.9.8).  La definición de la función-operador correspondiente (un método de basic_ios) puede ser la siguiente:

bool basic_ios::operator!() {   return fail(); }

Su existencia permite expresiones condicionales del tipo:

if (! cin) { ... }

§5  Control de flujo

Existen una serie de métodos que sirven para controlar el estado del flujo.

§5.1  iostate rdstate() const;

El valor state del flujo ( 5.3.2a) puede ser interrogado mediante este método; aunque el valor devuelto debe ser interpretado analizando sus bits individuales mediante las constantes definidas en ios_base para este efecto. Como veremos a continuación, algunos métodos de basic_ios chequean directamente sus bits; en cambio, otros se basan en estos valores para su funcionamiento.

Como ejemplo de uso, supongamos que tenemos un filestream myStream asociado a un fichero:

if (mySteam.eof())

   cout << "Alcanzado fin de fichero!!" << endl;

if (! myStream.good()) {

   if (myStream.rdstate() == ios::badbit) {

        cout << "Error fatal en el flujo!!" << endl;

   } else if (myStream.rdstate() == ios::failbit) {

        cout << "Error de operación en el flujo!!" << endl;

   } else {

        cout << "Errores varios en el flujo!!" << endl;

   }

}

§5.2  void clear(iostate state = goodbit);

Su invocación suele realizarse utilizando el valor por defecto (sin argumentos). Su comportamiento es como sigue:  

Si  (rdstate() & exeptions()) == 0 la función termina sin ninguna acción (el flag de estado ya estaba "limpio"). En caso contrario, se lanza una excepción del tipo basic_ios::failure.

Después que la invocación ha terminado, si rdbuf() != 0 (existe buffer intermedio), state = rdstate(). En caso contrario rdstate() = state | ios_base::badbit.

§5.3  void setstate(iostate state);

La acción de este método es realizar la invocación clear(rdstate() | state). Es decir, invocar el método clear() con un argumento que es el OR inclusivo ( 4.9.3) entre el estado actual y el de la máscara state pasada como argumento.

En caso de fallo o error durante las operaciones de E/S, los filestreams realizan una invocación setstate(failbit).  Este método puede a su vez lanzar una excepción de tipo ios_base::failure. Por esta razón, aunque es aconsejable englobar las operaciones con filestreams en bloques try - catch y utilizar el mecanismo de excepciones ( 1.6) para controlar las incidencias, la primera medida antes y después de una operación E/S importante es comprobar el estado .

Ejemplo: comprobación genérica de operación:

try {

  ifstream objFile;

  objFile.open("somefile.txt");

  ...

  ...

} catch( ios_base::failure& var ) {

    cout << var.what() << endl;

}

  ...

§5.4  bool good() const;

Devuelve rdstate() == 0 . Si devuelve cierto significa que todos los bits de rdstate ( 5.3.2a) están a cero.

§5.5  bool eof() const;

Devuelve true si el bit eofbit de rdstate ( 5.3.2a) está seteado (1) y false en caso contrario (0).

§5.6  bool fail() const;

Devuelve true si están seteados failbit o badbit en rdstate ( 5.3.2a).  false en caso contrario..

§5.7  bool bad() const;

devuelve true si batbit está seteado (1).

§5.8  exceptions()

Este método existe en dos versiones:

iostate exceptions() const;

Devuelve una máscara indicando cual de los bits de iostate es responsable de la excepción que se lanzará. Si no hay circunstancia excepcional que señalar devuelve cero.

void exceptions(iostate except);

Setea la máscara de excepciones al valor indicado por el argumento except. A continuación realiza una invocación clear(rdstate()).

§6  Control del buffer interno

Se incluyen varios métodos que permiten controlar aspectos del flujo relacionados con el buffer interno (streambuf  5.3.2f).

§6.1  tie()

basic_ostream<charT,traits>* tie() const;

basic_ostream<charT,traits>* tie(basic_ostream<charT,traits>* tiestr);

Ambos métodos se refieren a la posibilidad de enlazar "tie" dos flujos entre sí. El concepto "enlazar" debe entenderse aquí como sincronizar ( 5.3.2f). Cuando un flujo está sincronizado o "enlazado" con otro, el buffer del flujo de salida es vaciado antes que ocurra una operación de E/S en el otro flujo. Como se deduce de la inspección del segundo método, el enlazado de un flujo solo puede realizarse con otro que sea de salida (puede ser un iostream 5.3.2e).

Ambas formas devuelven un puntero a la secuencia de salida (ostream) sincronizada con el flujo que realiza la invocación. La primera se limita a devolver el puntero correspondiente. La segunda forma asocia (sincroniza) el flujo del objeto que invoca el método, con el flujo señalado por el puntero pasado como argumento (observe que debe ser un flujo de salida). Hecho esto, devuelve un puntero con la asociación anterior. Pasando un puntero nulo ( 4.2.1), el flujo invocante no queda sincronizado con ningún otro.

La sincronización permite que se completen determinadas operaciones en un flujo antes que se realicen otras operaciones en el flujo asociado. Esta condición es deseable de determinadas circunstancias. Por ejemplo:

cin >> num; cout << num;

En estas circunstancias, es muy conveniente que no se inicie la operación de cout antes que el buffer intermedio de cin haya sido vaciado completamente ("flushed"). De hecho, los flujos estándar ( 5.3) están por defecto sincronizados entre sí. Por ejemplo, después que el objeto cin se ha iniciado, cin.tie() devuelve &cout, y wcin.tie() devuelve &wcout. Después de iniciado, cualquier otro flujo genérico devuelve 0 (ver init()).

§6.2  rdbuf()

basic_ios dispone de dos métodos para controlar el buffer intermedio (streambuf) que controla la capa de transporte ( 5.3).  Ambos métodos devuelven un puntero al referido almacenamiento intermedio:

basic_streambuf<charT,traits>* rdbuf() const;

basic_streambuf<charT,traits>* rdbuf(basic_streambuf<charT,traits>* sb);

El primero simplemente devuelve un puntero al buffer interno. El segundo asocia al flujo el streambuf señalado por el puntero sb. Si se pasa un puntero nulo, el flujo queda en estado de error poniéndose a 1 el badbit de state ( 5.3.2a).

Nota:  Generalmente el objeto streambuf no es accedido directamente, sino a través de los métodos de los objetos filestream, que realizan estos accesos de forma transparente para el usuario. No obstante, en caso necesario, rdbuf() permite acceder a las propiedades y métodos del streambuf de cualquier flujo ( 5.3.2f).

§6.3  basic_ios& copyfmt(const basic_ios& rhs);

El método devuelve una referencia al propio objeto *this que realiza la invocación, al que llamaremos caller.  Su efecto es asignar a dicho objeto las propiedades correspondientes del iostream pasado como argumento; con las matizaciones siguientes:

  • rdstate() y rdbuf() permanecen sin cambios. Es decir, el estado actual del flujo (goodbit, badbit, eofbit y failbit) y su buffer interno permanecen inalterados.

    Entre otras consecuencias, el hecho de que el buffer interno no se vea afectado, significa que el estado abierto/cerrado de caller no se modifica. Por ejemplo, si caller es un flujo asociado a un fichero de disco de estado actual abierto (is_open() == true) y rhs está previamente cerrado, caller sigue abierto después de la invocación caller.copyfmt(rhs);

  • La máscara de excepciones de caller es alterada utilizando una invocación exeptions(rhs.except) .

  • Se copia el contenido de las matrices de rhs señaladas por iword() y pword(), de forma que los valores devueltos por rhs.iword(indx) o rhs.pword(indx) para un índice indx obtenido con xalloc(), es el mismo que en caller.iword(indx) y caller.pword(indx) respectivamente.

En realidad copyfmt() es lo más parecido a un constructor-copia ( 4.11.2d4) que puede existir en el universo de los iostreams.  Ya hemos indicado que generalmente estos objetos representan conexiones con dispositivos físicos que, por lo general, no pueden ser duplicados miembro-a-miembro en un sistema determinado.

El comportamiento de copyfmt() respecto del trío iword()/pword()/xalloc() de ios_base ( 5.3.2a1) se esquematiza en el seguiente ejemplo:

using namespace std;

static int ind1 = ios_base::xalloc();       // L.2

static int ind2 = ios_base::xalloc();

ifstream myFile("someFile.txt");            // L.5

myFile.iword(ind1) = 4321L;

cout << myFile.iword(ind1);                 //-> 4321

(string*) myFile.pword(ind2) = new string[10];   // L.9

*((string*) myFile.pword(ind2)) = "Hola MUNDO";

cout << *((string*) myFile.pword(ind2));    //-> Hola MUNDO

ifstream newFile("otherFile.txt");          // L.13

cout << newFile.iword(ind1);                //-> 0

cout << newFile.pword(ind2);                //-> 0

newFile.copyfmt(myFile);                    // L.17

cout << newFile.iword(ind1);                //-> 4321

cout << *((string*) newFile.pword(ind2));   //-> Hola MUNDO

myFile.iword(ind1) = 1234L;                 // L.21

*((string*) myFile.pword(ind2)) = "Hola LUNA.";

cout << myFile.iword(ind1);                 //-> 1234

cout << newFile.iword(ind1);                //-> 4321

cout << *((string*) newFile.pword(ind2))    //-> Hola LUNA

Comentario:

En L2/L3 se comienza con sendas invocaciones a xalloc para obtener dos índices estáticos, ind1 e ind2.  En L5 se crea un flujo, myFile asociado a un fichero (puede ser cualquier otro). A continuación se utiliza iword() para almacenar en el primer "slot" un long. La sentancia L7 es una comprobación de que efectivamente el "slot" señalado por ind1 contiene el valor esperado.

El siguiente grupo realiza una operación análoga con pword().  L9 crea un objeto string en el montón ( 1.3.2) mediante el operador new, y el puntero devuelto es asignado al "slot" proporcionado por pword() sobre ind2.  L10 inicia el objeto creado y L11 es una comprobación del valor almacenado.

L13 crea un nuevo flujo newFile.  Las dos sentencias que siguen sirven para verificar que sus "slots" de almacenamiento iword/pword están actualmente vacíos en ambos índices.

L17 copia las propiedades del flujo inicial en el nuevo. Las sentencias siguientes muestran como ahora se han copiado las áreas de almacenamiento del flujo inicial en el nuevo. Utilizando los índices sobre el nuevo flujo se obtienen los resultados esperados.

El último grupo de sentencias desvela la naturaleza de la copia efectuada: En L21 y L22 cambiamos los valores almacenado en los slots del primer flujo por nuevos valores. L23 y L24 sirven para comprobar como los valores almacenados en los "slots" numéricos de ambos flujos son ahora distintos (se trata de almacenamientos independientes).  La última sentencia sirve para comprobar que los almacenamientos pword de ambos flujos siguen señalando al mismo objeto (en el montón).

§6.4  fill()

char_type fill() const; char_type fill(char_type ch);

Estos métodos permiten respectivamente interrogar y fijar el carácter que será utilizado en los procesos de ajuste del ancho del campo ( 5.3.2a3).  El primero devuelve el carácter actual. El segundo establece un nuevo carácter y devuelve el valor existente en el momento de la invocación.

§7  Conversión de tipos

Existen dos métodos auxiliares que permiten transformar un carácter del flujo en su correspondiente versión ancha (si es un flujo de caracteres normales) o estrecha, si es un flujo de caracteres wchar_t.

   char narrow(char_type c, char dfault) const;

Utiliza el localismo del flujo para obtener la versión estrecha del carácter ancho c pasado como argumento. Si no existe equivalencia posible se devuelve el carácter dfault pasado como segundo argumento.

   char_type widen(char c) const;

Utiliza el localismo del flujo para convertir un carácter estrecho c pasado como argumento en el equivalente ancho; este es el valor devuelto.

§8  Constructores

La clase dispone de dos constructores [1] y un método que está también relacionado con el proceso de inicialización de los objetos creados.

   explicit basic_ios(basic_streambuf<charT,traits>* sb);

Constructor público explícito explicit ( 4.11.2d1) que recibe un puntero sb a un buffer interno del mismo tipo de la plantilla. Los miembros del nuevo objeto se inician mediante una invocación init(sb) .

   basic_ios();

Este método protegido crea un objeto dejando sus miembros sin inicializar. La inicialización de sus miembros debe efectuarse mediante una invocación posterior a su método init(). Si el objeto es destruido antes, el comportamiento es indefinido [2].

   void init(basic_streambuf<charT,traits>* sb);

Este método recibe un puntero del mismo tipo que el constructor explícito. Una vez que se ha ejecutado, los miembros del objeto son iniciados con los valores correspondientes de la tabla:

Miembro valor
rdbuf() sb
tie() 0
rdstate() goodbit si sb no es un puntero nulo. En caso contrario badbit
exceptions() goodbit
flags() dec |  skipws
width() 0
precision() 6
fill() widen('\x20')
getloc() Una copia del objeto devuelto por locale()
iarray Un puntero nulo
parray Un puntero nulo

  Inicio.


[1]  El estándar prevé así mismo la existencia de un tercer constructor privado que no está definido.

[2]  Puede verse que el comportamiento de los constructores es bastante atípico. En estos objetos no es el constructor el encargado de la correcta inicialización del objeto sino una función auxiliar. La razón de esta anomalía (bastante insólita por cierto) hay que buscarla en las especialísimas condiciones de creación de los objetos iostreams.

[3]  "Operator overloading". Dan Saks. Cpp Users Journal Julio 1992.