Archivo

Entradas Etiquetadas ‘almacén’

¿ Y tú qué eres ? Identificando clases heredadas en C++

Viernes, 9 de Diciembre de 2011 Gaspar Fernández Sin comentarios

blogvision_estudios_small

En ocasiones, sobre todo cuando estamos con temas de herencia entre clases, tenemos la necesidad de consultar de qué tipo es una instancia. Es decir, tenemos una clase Mascota, y clases Perro y Gato (subclases de la primera). Imaginad que en el código hay muchas funciones, que queremos que sean comunes para todas las mascotas, y por tanto, aceptarán como parámetro una instancia de Mascota, pero en su ejecución, necesitan en un momento saber de qué tipo concreto son (Perro o Gato). ¿ Cómo podemos averiguarlo ?

Lo podemos hallar con dynamic_cast. dynamic_cast forma parte de la RTTI (RunTime Type Information / Información de tipos en tiempo de ejecución), y es capaz de guardar los datos del tipo específico de una instancia y utilizarla durante la ejecución del programa (no sólo en la compilación). Esto no está en la especificación original de C++ (del 1983), sino que fue introducido en 1991, además, en algunos compiladores no viene activado por defecto (según Wikipedia, es lógico, ya que esto puede causar un coste, tanto en memoria, espacio del ejecutable y uso de memoria.)

Una forma para comprobar el tipo que estamos utilizando es la siguiente:

1
2
3
4
5
Perro *a = dynamic_cast<Perro *>(m);
  if (a!=NULL)
    {
      // Acciones con una instancia de Perro llamada a
    }

En este caso estamos comprobando que nuestra Mascota es un Perro.

A continuación vemos el ejemplo completo, en el que tenemos una serie de mascotas y vamos a llevarlas al veterinario, pero para eso, el veterinario determina qué tipo de mascota es y lo deriva al doctor adecuado (es un ejemplo). El ejemplo estará disponible para descarga al final del post.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
#include <string>

using namespace std;


class Mascota
{
public:
  Mascota(string nombre):nombre(nombre)
  {
  }
  virtual ~Mascota()
  {
  }

  string getNombre()
  {
    return nombre;
  }
private:
  string nombre;
};

class Perro : public Mascota
{
public:
  Perro(string nombre):Mascota(nombre)
  {
  }
};

class Gato : public Mascota
{
public:
  Gato(string nombre):Mascota(nombre)
  {
  }
};

class Iguana : public Mascota
{
public:
  Iguana(string nombre):Mascota(nombre)
  {
  }
};

void veterinarioPerro(Perro *p)
{
  cout << "Ponemos una inyección a tu perro " << p->getNombre() << endl;
}

void veterinarioGato(Gato *g)
{
  cout << "Ponemos una inyección a tu gato " << g->getNombre() << endl;
}

void veterinario(Mascota *m)
{
  cout << "Vas a llevar a tu mascota al veterinario..." << endl;

  Perro *a = dynamic_cast<Perro *>(m);
  if (a!=NULL)
    {
      veterinarioPerro(a);
    }
  else if (Gato *a = dynamic_cast<Gato *>(m))
    {
      veterinarioGato(a);
    }
  else
    {
      cout << "No sé qué tipo de mascota tienes" << endl;
    }
}

int main()
{
  Mascota *m;
  Mascota *m2;
  Mascota *i;

  m=new Gato("Mini");
  m2=new Perro("Rufus");
  i= new Iguana("Igor");

  veterinario(m);
  cout << endl;
  veterinario(m2);
  cout << endl;
  veterinario(i);
}

Tenemos que tener en cuenta un requisito primordial para dynamic_cast, y es que la clase sobre la que hagamos la llamada, tiene que ser una clase polimórfica, y será polimórfica cuando contenga al menos un método virtual. Si no sabemos qué método puede ser virtual en la clase, siempre podemos hacer que sea el destructor.

Aunque, por poner un ejemplo de más utilidad, imaginemos que en lugar de tener variables disponibles, cuyo control podemos llevarlo a mano, tenemos un vector de mascotas, y vamos a llevarlas a todas al veterinario. Sólo pego aquí el main(), en la descarga al final de la página estará el ejemplo completo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main()
{
  vector <Mascota *>mis_mascotas;
  Mascota *m;
 
  m=new Gato("Mini");
  mis_mascotas.push_back(m);
  m=new Perro("Rufus");
  mis_mascotas.push_back(m);
  m=new Iguana("Igor");
  mis_mascotas.push_back(m);
  m=new Perro("Ralph");
  mis_mascotas.push_back(m);
  m=new Gato("Pitu");
  mis_mascotas.push_back(m);
  m=new Perro("Pluto");
  mis_mascotas.push_back(m);
  m=new Gato("Kity");
  mis_mascotas.push_back(m);
  m=new Iguana("Motorcillo");
  mis_mascotas.push_back(m);

  for (vector<Mascota*>::iterator i=mis_mascotas.begin(); i!=mis_mascotas.end(); i++)
    {
      veterinario(*i);
      cout<<endl;
    }
}

Otra forma es utilizar typeid() para averiguar la clase a la que pertenece la instancia. Lo hacemos en la función veterinario(), es necesario incluir la biblioteca :

1
2
3
4
5
6
7
8
9
10
11
12
void veterinario(Mascota *m)
{
  cout << "Vas a llevar a tu mascota al veterinario..." << endl;

   cout << typeid(*m).name() << endl;
  if (typeid(*m)==typeid(Perro))
    veterinarioPerro((Perro*)m);
  else if (typeid(*m)==typeid(Gato))
    veterinarioGato((Gato*)m);
  else
    cout << "No sé qué tipo de mascota tienes" << endl;
}

Algo que también se puede hacer es averiguar el nombre de la clase e imprimirlo con cout, aunque intuitivamente no podremos, porque normalmente el nombre de los tipos no llegan al programa compilado, cuando se compila con soporte RTTI, tenemos la opción de almacenar estos nombres, así que cuidado al poner los nombres de las clases, que se podrá ver en el resultado final.
Para poner por pantalla los nombres de las clases, debemos hacer lo siguiente (pongo sólo la parte de main() correspondiente a recorrer el vector):

1
2
3
4
for (vector<Mascota*>::iterator i=mis_mascotas.begin(); i!=mis_mascotas.end(); i++)
    {
      cout << typeid(**i).name() << endl;
    }

typeid(instancia).name() nos devuelve en un formato textual el nombre de la clase. En el ejemplo hay ** (dos asteriscos) porque el iterador es un puntero a lo que estamos recorriendo, que son punteros a Mascota.
Hay que tener cuidado, porque esta opción no es igual en todos los compiladores, puede que algunos compiladores te devuelvan el nombre tal cual, y otros te devolverán el nombre con algún número o caracteres especiales en el caso de ser punteros, por ejemplo. El más claro ejemplo es GXX cuyo resultado es:

4Gato
5Perro
6Iguana
5Perro
4Gato
5Perro
4Gato
6Iguana

El número que precede los nombres de clase es el número de caracteres que tiene dicho nombre: Gato = 4, Perro = 5, Iguana = 6 . Si probamos con Mascota, saldrá un 7. Y no hay ninguna forma de hacerlo portable a diferentes compiladores por lo que tendremos que hallar soluciones específicas en el caso de que queramos tener el nombre de la clase en formato plano.

En GXX (g++) podemos utilizar lo siguiente, es una función que arregla el nombre para los humanos, no sólo de clases, sino más cosas. Podemos hacer lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
string demangle(const char *input)
{
  int status;
  string res(abi::__cxa_demangle(input, 0, 0, &status));
  return res;
}

int main()
{
  vector <Mascota *>mis_mascotas;
  Mascota *m;
 
  m=new Gato("Mini");
  mis_mascotas.push_back(m);
  m=new Perro("Rufus");
  mis_mascotas.push_back(m);
  m=new Iguana("Igor");
  mis_mascotas.push_back(m);
  m=new Perro("Ralph");
  mis_mascotas.push_back(m);
  m=new Gato("Pitu");
  mis_mascotas.push_back(m);
  m=new Perro("Pluto");
  mis_mascotas.push_back(m);
  m=new Gato("Kity");
  mis_mascotas.push_back(m);
  m=new Iguana("Motorcillo");
  mis_mascotas.push_back(m);

  for (vector<Mascota*>::iterator i=mis_mascotas.begin(); i!=mis_mascotas.end(); i++)
    {
      cout << demangle(typeid(**i).name()) << endl;
    }
}

He utilizado una función para simplificar un poco esto, ya que __cxa_demangle(), tiene varios parámetros y nos interesa tener una función que la llame con los parámetros por defecto, para hacer la tarea más directamente. También tenemos que incluir

En G++ podemos utilizar la opción -fno-rtti si no queremos compilar nuestros programas en C++ con este soporte.

Descárgate los ejemplos: dynamic_types.tar.bz2

Almacenando elementos a pares en C++

Viernes, 22 de Julio de 2011 Gaspar Fernández Sin comentarios

En numerosas ocasiones necesitamos almacenar dos datos de diferente o igual tipo bajo la misma variable. Es más, en ocasiones estarán relacionados y no debemos separarlos o no tendría sentido.

La forma más inmediata es almacenar cada dato por su lado y diseñar los algoritmos de modo que siempre vayan los dos datos juntos, aunque tendremos un problema:

  • Será difícil de mantener, tenemos siempre que asegurar que los datos siempre vayan juntos.
  • Tendríamos que crear más documentación, o el que venga detrás de nosotros a hacer el programa lo pasará mal.
  • Debemos crear funciones / métodos para manejar los datos.

De todas formas, C++ , en la biblioteca estándar contienen muchas cosas ya hechas y esta es una de ellas; utilizando la clase pair podemos hacer fácilmente la asignación / comparación / lectura de pares de datos. De la siguiente manera:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

using namespace std;

int main()
{
  pair<int,string> p;

  p.first=23;
  p.second="Probandoooo...";

  cout << p.first << endl;
  cout << p.second << endl;

  p = make_pair(10, "Segunda prueba");

  cout << p.first << endl;
  cout << p.second << endl;

  return 0;
}

Fácilmente podremos crear una instancia de pair, especificando los tipos de dato a almacenar y asignarle los datos (para los ejemplos utilizaré los tipos int y string; aunque puede ser cualquier tipo de dato, clases, etc:

  • Desde el mismo constructor: pair p(999, “Dato de ejemplo”);
  • Desde los atributos first y second (como en el ejemplo)
  • Con la función make_pair(), en la que podemos introducir los objetos directamente

De esta forma, tal vez queramos introducir elementos de configuración, o asignar elementos de tipo clave=>valor, asignar un identificador a un elemento, o símplemente pasar dos datos juntos con un mismo elemento.

Pero si lo que en realidad queremos es almacenar un conjunto de datos (claves y valores) podemos utilizar map o multimap. El objetivo es introducir muchos tipos de elementos con su equivalencia (siendo ésta otro tipo de elemento distinto):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <map>

using namespace std;

int main()
{
  map <int, string> m;
  map <int, string>::iterator i;
 
  // Forma 1
  m.insert(pair<int, string>(10, "Elemento numero diez"));
  // Forma 2
  m[20]="Elemento numero veinte";
  // Forma 3
  m.insert(m.begin(), pair<int, string>(30, "Elemento numero treinta"));

  // Insertamos más elementos
  m[40]="Elemento numero cuarenta";
  m[50]="Elemento numero cincuenta";
  m[60]="Elemento numero sesenta";
  m[70]="Elemento numero ochenta";
 
  // Listamos los elementos
  for (i=m.begin(); i!=m.end(); i++)
    {
      cout << "Elemento " << i->first << " = " << i->second << endl;
    }

  cout << endl;
  // Buscamos un elemento
  i=m.find(30);
  cout << "Elemento 30 => "<<i->second<<endl;

  cout << endl;

  // Buscamos un elemento de otra forma
  cout << "Elemento 40 => "<<m[40]<<endl;

  cout << endl;

  // Cuando un elemento no existe...
  i=m.find(35);
  if (i==m.end())
    cout << "Elemento no existe" <<endl;
  else
    cout << "Elemento 35 => " << i->second <<endl;

  // Devuelve un elemento vacío (o recién construido)
  cout << "Elemento 55: "<< m[55] << endl;

  return 0;
}

Para escribir un poco menos a la hora de definir los tipos podemos utilizar typedef:

1
2
3
4
5
6
7
8
9
typedef map <int, string> mis;  // Map Int String
typedef pis <int, string> mis;  // Pair Int String

int main()
{
   mis m;
   mis::iterator m;
   m.insert(pis(10, "Cadena"));
}

O podemos utilizar el #define de toda la vida:

1
2
3
4
5
6
7
8
9
#define is int, string

int main()
{
  map<is> m;
  map<is>::iterator i;
 
  m.insert(pair<is>(10, "Elemento numero diez"));
}

Aunque tendremos que tener cuidado con lo que pongamos con #define ya que los cambios serán globales.

Función curiosa: preg_replace_callback (PHP)

Lunes, 15 de Marzo de 2010 Gaspar Fernández 2 comentarios

A veces tenemos la necesidad de reemplazar un texto dentro de una cadena larga. Por ejemplo, el uso principal que le doy a esta función es el procesamiento de plantillas para páginas web, en donde tengo la página en HTML puro por un lado, y en su interior hay ciertas palabras clave, por ejemplo —seccionA—, —fotoUsuario—, —menuSesion—, etc; y en la web definitiva, aparecerá otra cosa en lugar de ese texto, aparecerá el objeto al que hace referencia.
Aunque cada cadena de texto seccionA, fotoUsuario, requiere un procesamiento distinto y es posible que no tengamos por qué procesar siempre todas las equivalencias de todos los textos. Queremos en definitiva reemplazar —seccionA— por el resultado de una función que ejecutaremos cuando encontremos ese texto. Sería como:

1
2
3
4
$disparadores=array("---seccionA---", "---fotoUsuario---", "---menuSesion---");
$reemplazos=array(dibuja_seccionA(), get_fotoUsuario(), dibuja_menuSesion);

$web=str_replace($disparadores, $reemplazos, $web);

Aunque, como dijimos, en el ejemplo anterior, se tienen que ejecutar dibuja_seccionA(), get_fotoUsuario() y dibuja_menuSesion().

Pero podemos aprovecharnos de las expresiones regulares, para extraer texto que esté entre “—” y “—” y pasar lo que hay entre medias a otra función que decidirá qué hacer; eso hace preg_replace_callback().

1
2
3
4
5
6
7
8
9
10
function quehacer($texto)
{
  switch ($texto[1]) // [0] devuelve: ---texto---, [1] devuelve texto
  {
     case 'seccionA': return dibuja_seccionA();
     case 'fotoUsuario': return get_fotoUsuario();
     case 'menuSesion': return dibuja_menuSesion();
  }
}
$web=preg_replace_callback('/---([a-zA-Z0-9_]*)---/', 'quehacer', $web);

Hasta aquí bien, el primer parámetro es una expresión regular (es un tema muuuy extenso), el segundo es la función a la que llamamos cuando encontramos el texto, y el tercero es de dónde lo sacamos. Bueno, ahora surge un problema, imaginad que necesito pasarle parámetros a esa función callback. Lo podemos hacer así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function guardar_datos($data=null)
{
  static $guardados; // Una variable estática, muy importante

  // Si se especifica $data, lo guardamos en $guardados, al ser estática, aunque salgamos de la función, el valor de la variable no se perderá.
  if ($data)
    $guardados=$data;
 
  return $guardados;
}

function quehacer($texto)
{
  $datos = guardar_datos();

  switch ($texto[1]) // [0] devuelve: ---texto---, [1] devuelve texto
  {
     case 'seccionA': return dibuja_seccionA();
     case 'fotoUsuario': return get_fotoUsuario($datos);
     case 'menuSesion': return dibuja_menuSesion($datos);
  }
}

guardar_datos(array($datos_usuario, $datos_sesion));
$web=preg_replace_callback('/---([a-zA-Z0-9_]*)---/', 'quehacer', $web);

Nota: las variables $datos_usuario, $datos_sesion y $web no están definidas, como esto es parte de un programa, hay que imaginar un poco cuál será la información que contendrán.

Visita otras webs de la red