1.2. Programación OO en C++

Introducción

Según [3] un sistema se califica como Orientado a Objetos cuando reúne las características de: abstracción, encapsulación, herencia y polimorfismo.
  • Abstracción consiste en la generalización conceptual de un determinado conjunto de objetos y de sus atributos y propiedades.
  • Encapsulación se refiere a la capacidad de agrupar en un entorno distintos elementos.
  • Herencia está fuertemente ligada a la reutilización de código. En C++ se implementa mediante un mecanismo denominado derivación de clases, de modo que se posibilita el uso del código creado para la clase base por parte de sus clases derivadas.
  • Polimorfismo se refiere a la posibilidad de acceder a un variado rango de funciones distintas a través de un mismo interfaz.

Clases y Objetos

La abstracción y encapsulación en C++ están representada por el concepto de clase. Abstracción en el sentido de que una clase define una serie de atributos genéricos de determinados objetos con características comunes. Y encapsulación en cuanto a que la clase comprende tanto los datos de que constan los objetos como los procedimientos que permiten manipularlos.

Una clase en C++ puede verse como un struct en C que, además de variables, admite funciones y permite diferentes niveles de acceso en función de unas etiquetas.

La finalidad de las clases en C++ es proporcionar una herramienta para crear nuevos tipos. Una clase es un tipo definido por el programador. La idea fundamental en definir un nuevo tipo consiste en mantener separados los detalles de implementación de las propiedades para su uso.

Un objeto es una instancia de una clase. Se crea mediante una función denominada constructor. Los constructores tienen el mismo nombre que la clase y su objetivo es realizar las funciones necesarias de inicialización. Una clase puede tener varios constructores, cada uno con diferentes argumentos.

Si un objeto hace uso de recursos (ficheros, memoria, mecanismos de bloqueo, etc.) es conveniente incluir el código necesario para su liberación en un destructor, que será llamado de manera automática cuando el objeto salga fuera de ámbito. El nombre del destructor consiste en el nombre de la clase precedido del símbolo '~'

Programa Ejemplo:Mónica y Alberto son objetos de clase Persona. La clase Persona tiene dos atributos o miembros (Nombre y Edad), un método o función miembro (Habla), un constructor (Persona) y un destructor (~Persona).
#include <iostream>
#include <cstring>

using namespace std;

class Persona {

public:
	char* Nombre;
	int Edad;

	Persona(const char* inNombre, int inEdad);
	~Persona();
	void Habla();

};

Persona::Persona(const char* inNombre, int inEdad) {
	Nombre = new char[strlen(inNombre)+1];
	strcpy(Nombre,inNombre);
	Edad = inEdad;
}

Persona::~Persona() {
	cout << Nombre << " ha muerto a los " << Edad << " años." << endl;
}

void Persona::Habla() {
	cout << "hola! mi nombre es " << Nombre << endl;
}

int main(int argc, char **argv) {

	Persona alberto("Alberto",25);
	Persona monica("Mónica",28);

	alberto.Habla();
	monica.Habla();

	if (alberto.Edad > monica.Edad) {
			cout << alberto.Nombre << " es mayor que " << monica.Nombre << endl;
	} else {
			cout << alberto.Nombre << " NO es mayor que " << monica.Nombre << endl;
	}

	return 0;
}
Ejecución del programa:
hola! mi nombre es Alberto
hola! mi nombre es Mónica
Alberto NO es mayor que Mónica
Mónica ha muerto a los 28 años.
Alberto ha muerto a los 25 años.

El puntero implícito this

Para referirse a miembros de un objeto sin ambigüedades a veces es útil utilizar el puntero implícito this. Por ejemplo, la función Habla() de la clase Persona podría haberse escrito como
void Persona::Habla() {
	cout << "hola! mi nombre es " << this->Nombre << endl;
}
donde this se entiende implícitamente declarado como
Persona const* this;

Herencia

Julián es un objeto de clase Asesino, que hereda de Persona los métodos y atributos y añade el método público Mata y el atributo privado NumCrimenes. La clase Asesino se denomina clase derivada de Persona. En C++ es posible derivar de más de una clase.
...

class Asesino : public Persona  {

private:

	int NumCrimenes;

public:

    Asesino(const char* inNombre, int inEdad);

    ~Asesino();

    void Mata(Persona* inVictima);

};

Asesino::Asesino(const char* inNombre, int inEdad) : Persona(inNombre,inEdad) {
	NumCrimenes = 0;
}

Asesino::~Asesino() {
	cout << "Tras dejar " << NumCrimenes << " crímenes a sus espaldas ";
}

void Asesino::Mata(Persona* inVictima) {
	NumCrimenes++;
    delete inVictima;
}

int main(int argc, char **argv) {

	Persona alberto("Alberto",25);
	Persona monica("Mónica",28);

	Persona* victima1 = new Persona("Juan",22);
	Persona* victima2 = new Persona("María",19);
	Asesino julian("Julián",35);

	alberto.Habla();
	monica.Habla();
	julian.Habla();

	julian.Mata(victima1);
	julian.Mata(victima2);

	if (alberto.Edad > monica.Edad) {
		cout << alberto.Nombre << " es mayor que " << monica.Nombre << endl;
	} else {
		cout << alberto.Nombre << " NO es mayor que " << monica.Nombre << endl;
	}

	return 0;
}
Ejecución del programa modificado:
hola! mi nombre es Alberto
hola! mi nombre es Mónica
hola! mi nombre es Julián
Juan ha muerto a los 22 años.
María ha muerto a los 19 años.
Alberto NO es mayor que Mónica
Tras dejar 2 crímenes a sus espaldas Julián ha muerto a los 35 años.
Mónica ha muerto a los 28 años.
Alberto ha muerto a los 25 años.

Control de Acceso

Mediante las etiquetas publicprivateprotected y friend en la definición de una clase, se puede especificar la accesibilidad de sus miembros.
  • Los miembros public no tienen restricciones de acceso. Forman parte de la interfaz de la clase con el exterior
  • Los miembros private solamente son accesibles por otros miembros de la misma clase.
  • Los miembros protected son accesibles por los miembros de la misma clase y de sus clases derivadas.
Los miembros private y protected se usan para la funcionalidad interna de la clase, que no interesa conocer desde el punto de vista del uso de los objetos de la clase. El método Mata de la clase Asesino puede invocarse desde la función main porque es público, sin embargo si tratásemos de acceder al atributo privado NumCrimenes obtendríamos un error de compilación. Al ser privado solamente puede ser accedido por los métodos de la propia clase (como por ejemplo su destructor). Si fuera protegido (protected) sería también accesible por los métodos de clases derivadas.
  • Una función declarada como friend en una clase tiene acceso a los miembros privados de la clase sin ser estrictamente miembro de la misma. Esto es de utilidad por ejemplo si se necesita acceder a miembros privados de mas de una clase. Si tenemos las clases Vector y Matriz, podemos definir el operador producto de Matriz por Vector como función amiga de ambas (ejemplo extraído de [1]).

Polimorfismo

En C++ se establece mediante la sobrecarga de identificadores y operadores y mediante las funciones virtuales.

El término sobrecarga se refiere al uso del mismo identificador u operador en distintos contextos con distintos significados. Por ejemplo la suma de números reales y la suma de números imaginarios mediante el operador "+" da lugar a operaciones diferentes.

Mediante la sobrecarga de funciones un mismo nombre puede representar distintas funciones con distinto tipo y número de argumentos, lo que favorece la legibilidad del código.

Las funciones virtuales son funciones miembro de una clase destinadas a ser definidas o redefinidas en clases derivadas. Se denominan virtuales puras cuando solamente están definidas en clases derivadas (es decir, en la clase base no se implementan). Si una clase contiene métodos virtuales puros se denomina clase abstracta y no admite ser instanciada.

Ejemplo:
#include 
#include 

using namespace std;

class Abstracta {

public:

	// con el modificador 'virtual' en la declaración indicamos que es una función virtual
	// Al igualar a 0 en la declaración indicamos que la función es además virtual pura
	virtual void funcionVirtualPura() = 0;

};

class HijaDeAbstracta : public Abstracta {

public:

	HijaDeAbstracta(){};
	virtual ~HijaDeAbstracta(){};
	void funcionVirtualPura();
};

void HijaDeAbstracta::funcionVirtualPura(){
	cout << "Soy un objeto de clase HijaDeAbstracta y esta es mi versión de funcionVirtualPura()" << endl;
}

class NoAbstracta {

public:

	NoAbstracta(){};
	virtual ~NoAbstracta(){};
	virtual void funcionVirtual();

};

void NoAbstracta::funcionVirtual(){
	cout << "Soy un objeto de clase NoAbstracta y esta es mi versión de funcionVirtual()" << endl;
}

class HijaDeNoAbstracta : public NoAbstracta {

public:

	HijaDeNoAbstracta(){};
	virtual ~HijaDeNoAbstracta(){};
	virtual void funcionVirtual();
};

void HijaDeNoAbstracta::funcionVirtual(){
	cout << "Soy un objeto de clase HijaDeNoAbstracta y esta es mi versión de funcionVirtual()" << endl;
}

int main(int argc, char **argv) {

	NoAbstracta NoA;
	HijaDeNoAbstracta HijaDeNoA;
	HijaDeAbstracta HijaDeA;

	NoA.funcionVirtual();
	HijaDeNoA.funcionVirtual();
	HijaDeA.funcionVirtualPura();

	// Abstracta A; Esto generaría un error de compilación!

	return 0;
}
Ejecución:
Soy un objeto de clase NoAbstracta y esta es mi versión de funcionVirtual()
Soy un objeto de clase HijaDeNoAbstracta y esta es mi versión de funcionVirtual()
Soy un objeto de clase HijaDeAbstracta y esta es mi versión de funcionVirtualPura()


Composición y Agregación

La composición en C++ es el uso de clases como miembros de otras clases en la que existe una relación de pertenencia. Por ejemplo la clase persona podría estar compuesta por dos miembros de clase Brazo y dos de clase Pierna. La creación/destrucción de un objeto Persona implica la creación/destrucción de sus extremidades.

La agregación en C++ el el uso de clases como miembros de otras clases sin relación de pertenencia. La clase Duo está compuesta por dos miembros de clase Persona. Estas dos personas existen antes de la creación y permanecen después de la destrucción del dúo.


La diferencia entre composición y agregación en C++ es principalmente semántica, pudiendo ser muy similar su implementación.

Bibliografía

[1] - The C++ Programming Language. Bjarne Stroustrup. 3rd Ed. Addison Wesley 
[2] - Aprenda C++ como si estuviera en primeroJavier García de Jalón, José Ignacio Rodríguez, José María Sarriegui, Alfonso Brazález 
[3] - Programación Orientada a Objetos en C++. Ricardo Devís Botella. Paraninfo 
Comments