Archivo

Entradas Etiquetadas ‘objetos’

¿ 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

Programar en C++ puede llegar a ser frustrante

Miércoles, 20 de Octubre de 2010 Gaspar Fernández Sin comentarios

cuchilloSiempre se dice que una de las reglas de la programación es “escribir poco”, tenemos que aunque en ciertos lenguajes, tenemos que escribir dos veces las cosas. Por ejemplo, para un sencillo hola mundo con clases en C++ (se puede hacer todo en el mismo archivo, pero queremos el código bien organizado):
[ hwclass.h ]

1
2
3
4
5
6
7
8
class HolaMundo
{
 public:
  HolaMundo();
  ~HolaMundo();

  void coutVersion();
};

[ hwclass.cpp ]

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

using namespace std;

HolaMundo::HolaMundo()
{
  cout<<"Hola Mundo!!"<<endl;
}

HolaMundo::~HolaMundo()
{
  cout<<"Adiós Mundo!!"<<endl;
}

void HolaMundo::coutVersion()
{
  cout<<"HolaMundo Versión 1.0"<<endl;
}

[ main.cpp ]

1
2
3
4
5
6
7
#include "hwclass.h"

int main()
{
  HolaMundo hm;
  hm.coutVersion();
}

Si queremos compilar el proyecto podemos hacer:

$ g++ -o holamundo main.cpp hwclass.cpp

Bueno, a lo que voy, como veis en hwclass.cpp y hwclass.h se repiten algunas cosas, el nombre de la clase, y el nombre de los métodos cuando vamos a definir el código de cada uno… cuando en el archivo .h se definen decenas de métodos resulta muy repetitivo teclear una y otra vez lo mismo y más aún mantener el orden de los métodos.

Para ello quiero presentar dos aplicaciones:

lzz

Podemos descargarlo desde aquí, tiene multitud de opciones y es bastante potente. Aunque yo veo un gran defecto, tenemos que crear un fichero lzz, que luego el programa lo convertirá a cpp y hpp extrayendo la información según corresponda. Por otra parte, para generar el ejecutable, necesitamos el binario de lzz (como si de un compilador se tratara)

stubgen

Lo encontramos aquí, y aunque le falta alguna que otra cosilla como personalización de la documentación, es bastante potente y nos permite trabajar normalmente con nuestro .h y generar el .cpp con todos los métodos empezados, justo para que nosotros completemos el código.

Foto: ~Brenda_Starr~ (Flickr)
¿Por qué un cuchillo? Un stub en programación es un trozo de código incompleto (tal vez simula lo que va a hacer, o sólo es una pincelada)… stub se parece a stab (puñalada).

Clases abstractas e interfaces en PHP

Viernes, 4 de Junio de 2010 Gaspar Fernández 2 comentarios

objetoLos interfaces son bastante comunes en la programación orientada a objetos, en PHP, son algo así como una declaración de lo que debe implementar una clase. Tendrá información de los métodos (exclusivamente) que se deben implementar.

Las clases abstractas contienen atributos y métodos, algunos pueden estar implementados y otros pueden ser abstractos. Los métodos abstractos son aquellos que no están implementados. Como tienen métodos sin implementar no podemos crear una instancia de este tipo de clases; entonces, ¿ para qué valen ?
Los métodos abstractos, los debemos implementar en las clases derivadas.

Vemos que tanto interfaces como clases abstractas son muy parecidos, los dos muestran declaraciones que debemos implementar más adelante, pero veamos algunas diferencias:

  • Una interfaz no puede contener atributos ni métodos implementados, mientras que una clase abstracta sí que puede contenerlos
  • Una clase sólo puede heredas una clase abstracta, mientras que en una misma clase se pueden implementar varias interfaces.
  • Las clases abstractas pueden declarar los métodos con diferente visibilidad (public, protected, internal, etc; private no, esto indicaría que una clase derivada no podría ver el método, por tanto no tendría sentido; final tampoco, porque no admitiría implementaciones futuras, aunque una clase derivada sí podría contener un método como final), mientras que en los interfaces todos los métodos deben ser públicos (también static)
  • Los métodos que no implementamos en una clase abstracta, deben ser abstract, en un interface, todos los métodos son abstract, y ninguno debe ser implementado.

Por último, aunque no sea un ejemplo demasiado concreto, pondré un fragmento de código en el que se define una clase abstracta (Articulo, como un artículo de una tienda), y varios interfaces de algunos tipos de artículo. (Intentao demostrar todo lo que he contado antes, para nada es un ejemplo real):

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
<?php

abstract class Articulo
{
  private $stock;
  private $precio;

  abstract protected function venta($cantidad);

  abstract function calcula_precio_total($cantidad, $descuento);
}

interface En_caja
{
  function idiomas();
  function lectura($idioma);
}

interface Juguete
{
  function puedo_probarlo();
  static function probar();
  function tirar_al_suelo();
}

class HomerSapiens extends Articulo implements En_caja, Juguete
{
  protected function venta($cantidad)
  {
    ...
  }

  final function calcula_precio_total($cantidad, $descuento)
  {
    ...
  }

  function idiomas()
  {
    ...
  }

  function lectura($idioma)
  {
    ...
  }

  function puedo_probarlo()
  {
    ...
  }

  static function probar()
  {
    ...
  }

  function tirar_al_suelo()
  {
    ...
  }
}
?>

Foto: kevindooley (Flickr)

Conocer la posición de un objeto (Javascript)

Domingo, 4 de Abril de 2010 Gaspar Fernández Sin comentarios

Posteo un par de funciones para saber la posición absoluta (x,y) de un objeto en Javascript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getX( oElement )
{
  var iReturnValue = 0;
  while( oElement != null ) {
    iReturnValue += oElement.offsetLeft;
    oElement = oElement.offsetParent;
  }
  return iReturnValue;
}

function getY( oElement )
{
  var iReturnValue = 0;
  while( oElement != null ) {
    iReturnValue += oElement.offsetTop;
    oElement = oElement.offsetParent;
  }
  return iReturnValue;
}

Encontré la función getY en este foro.

Con estas funciones podremos saber la posición de los objetos con respecto a la ventana del navegador.

Categories: Javascript Tags: , , ,

Visita otras webs de la red