sábado, 13 de septiembre de 2014

C++, Puntero implícitoTHIS, friend

El puntero implícito this
Saltar el sistema de protección. ( Friend )
Miembros static de una clase
Funciones virtuales

El puntero implícito this


Cada objeto de una determinada clase mantiene su propia copia de los datos miembro de la clase, pero no de las funciones miembro, de estas sólo existe una copia para todos los objetos de la clase; es decir, cada objeto tiene su propia estructura de datos, pero todos comparten el mismo código para cada función miembro. De este modo si necesitamos que una función miembro conozca la identidad de cada objeto para el que ha sido llamada, en C++ debemos invocar un puntero al objeto denominado this. Así por ejemplo, si declaramos un objeto.


Empleado.set_empleado( );

Puntero This, C++

C++ define el puntero this para apuntar al objeto fecha1 de la forma:

CEmpleado  *const this = &Empleado1;

y si realizamos la misma operación con otro objeto empleado2,  empleado2.set_empleado( );  C++ define el puntero this, para apuntar al objeto empleado2, de la forma:
CEmpleado   *const this = &Empleado2;

Según lo expuesto, la función set_empleado puede ser definida de la forma:
void CEmpleado::set_empleado( )
{    cout << "nombre, ##   : "; cstr >> this->nombre;
cout << "apellido1, ## : "; cstr >> this ->apellido1;
cout << "apellido2, #### :"; cstr >> this -> apellido2;
}
Un ejemplo de esto.
do
empleado.set_empleado( );
while ( !empleado.ok_empleado( ));

En este caso, la función miembro ok_empleado conoce con exactitud a que objeto ha de ser aplicada, puesto que se ha expresado explícitamente. Pero ¿Que pasa con la función miembro Jefe_de, que se encuentra sin referencia directa alguna en el cuerpo de la función miembro ok_empleado?
                       
int Cempleado::ok_empleado( )
{
//...
if (Jefe_de (apellido1))
//...
}

En este otro caso la llamada no es explícita como en el anterior. Lo que sucede realmente, es que todas las llamadas a los datos y funciones miembro son referenciadas con un puntero implícito this, que contiene, como ya se ha dicho, la dirección del objeto por medio del cual se ha producido la llamada. Según esto también podríamos escribir.
if ( this -> Jefe_de(apellido1 ));
Normalmente no será necesario utilizar este puntero para acceder a los miembros de la clase, pero es útil cuando si se trabaja con estructuras dinámicas, también se puede utilizar la expresión (*this) miembro.

Saltar el sistema de protección. (Friend)

Los datos miembro de una clase declarados en la parte privada pueden manipularse únicamente por las funciones miembro de la clase, este es el modo de garantizar su integridad. El resultado es como una caja negra que realiza cierta función y que es reutilizable en cualquier otro programa.
En el caso de que una función no miembro de una determinada clase necesite acceder a los datos miembro privados de la misma, hay que declarar a dicha función amiga de la clase. Para esto se declara la función anteponiendo al nombre de la misma la palabra reservada friend.
Una función declarada friend de una clase CClase es una función no miembro que puede acceder a los miembros privados de la clase CClase.
Por ejemplo la función VisualizarVector de CClase no puede acceder a la clase CVector. Según lo expuesto este problema puede resolverse haciendo que la función VisualizarVector sea friend de la clase CVector esto es:
class CClase
{
void VisualizarVector( CVector &objvector);
};
class CVector
{
friend void VisualizarVector( CVector &objvector);
//...
};
De este modo la definición de la función VisualizarVector puede utilizar datos miembro declarados como private en la clase CVector. Una función friend se puede declarar en cualquier sección de la clase.

class C1;  // declaración
class C2
{
public:
int GetDato ( C1 &obj);
//...
};
class C1
{
friend int C2::GetDato( C1 &);
//...
};

Algunas veces puede ser también necesario que una clase C1 tenga acceso directo a los datos miembro privados de otra clase C2.
class C1
{
friend class C2;
//...
};

Jerarquía de clases con Friend

Una función friend de una clase base puede acceder solamente a los miembros de la clase  derivada que fueron heredados de la clase base. Es decir, la amistad no se hereda. Si la función es friend de la clase derivada, esta puede acceder a los miembros públicos y protegidos de la clase base.
Una clase friend de una clase base tiene acceso, además de a los miembros públicos, a los miembros privados y protegidos de la clase base, pero no tiene acceso a los miembros privados y protegidos de la clase derivada.  Sin embargo, una clase friend de una clase derivada de una clase base, no tiene acceso a los miembros privados y protegidos de dicha clase base. Esto indica que no hay una relación entre ambas clases friend.

Miembros static de una clase

Una clase es un tipo, no un objeto (Un objeto es un ejemplar de una clase determinada) cada objeto de una determinada clase tiene su propia copia de los datos miembro de la clase a la que pertenece, pero no de las funciones miembro.

Si declaramos un dato miembro de una clase como static, sólo existirá una copia de ese miembro, la cual será compartida por todos los objetos de esa clase y existirá aunque no existan objetos de esa clase. Un dato miembro de una clase declarado como static es una variable asociada con la clase, no con el objeto.
Un dato miembro static existirá aunque no se hayan declarado objetos de la clase.
Un dato miembro static debe ser declarado a nivel global. No se debe colocar la inicialización en un lugar donde se pueda producir más de una vez; por ejemplo, en un fichero de cabecera, ya que este puede ser cargado más de una vez. La inicialización de un dato static normalmente se colocará en el fichero fuente que contiene las definiciones de las funciones miembros de la clase.
class CCuenta
{
//...
static float Saldo;
};
// inicializar variables estáticas
float CCuenta::Saldo = 15,34;
//...
Una función miembro declarada como static podrá acceder solamente a miembros static de su propia clase. Este tipo de funciones no estarán asociadas con un objeto específico. Por lo que no tiene puntero this, y esto impide que puedan acceder a un miembro normal de su clase. Estas funciones se utilizan normalmente para actuar globalmente sobre todos los objetos de una clase.
class CCuenta
{
//...
static void SetSaldo ( float NuevoSaldo) { Saldo = NuevoSaldo;}
//...
static float Saldo;
};
// inicializar variables estáticas

float CCuenta::Saldo = 15,34;
void main ( )
{
//...
CCuenta::SetSaldo (20);
//...
}
Una función miembro static también puede llamarse utilizando la siguiente sintaxis:
nombre_objeto.SetSaldo(20);
Pero resulta engañosa ya que su acción no se ejecutará sobre un objeto en particular, sino sobre todos los objetos de la clase.
En definitiva, un miembro static de una clase no está asociado con un objeto individual de la clase, sino que lo está con la propia clase.

Funciones virtuales

Si se llama a una función miembro que está definida en la clase base y también está definida en la clase derivada, la función que realmente es invocada dependerá del tipo de objeto o del puntero que se utilice para invocar a la misma.
Si llamamos a una función que se define con el mismo nombre en varias clases, necesitamos que la función llamada pertenezca a la misma clase que el objeto referenciado.
Cuando se llama a una función miembro que está definida en la clase base y en la clase derivada, la función invocada depende del tipo de puntero. El propio sistema será el que se encargue de identificar en tiempo de ejecución la clase de los objetos apuntados. El mecanismo que utiliza C++ para esto es la función virtual.
Una función virtual es pues una función miembro pública o protegida de una clase base que puede ser redefinida en cada una de las clases derivadas. Una vez redefinida se accede a ella a través de un puntero o una referencia a su clase base.

Para declarar una función virtual hay que colocar la palabra clave virtual antes de la declaración de la función miembro de la clase base. Las redefiniciones que realicemos de esta función en las clases derivadas no necesitan incorporar en su declaración la palabra clave virtual; serán declaradas implícitamente.

class Albaran
{
//...

virtual void Visualizar( );
//...
};
La declaración de la función visualizar en la clase base le precede la palabra clave virtual. De este modo, tanto la función Visualizar de la clase base como la de las clases derivadas serán funciones virtuales. Una función global o static no podrá ser declarada virtual, ya que una función virtual sólo será llamada para objetos de su clase. Pero, una función virtual si podrá ser declarada friend de otra clase.

Llamar a una función virtual

La sintaxis para llamar a una función virtual es la misma que se utiliza para llamar a una función miembro normal. Una función virtual será invocada mediante una referencia o puntero a su clase base. La llamada a través de un puntero será automáticamente manipulada dependiendo del tipo del objeto apuntado. Si la llamada a la función virtual se hace a través de un objeto de una clase, el compilador resolverá la función a invocar basándose en el tipo de objeto.

Como regla general una llamada a una función virtual se resolverá en función del tipo de objeto para el que es invocada, mientras que una llamada a una función no virtual se resolverá en base al tipo de puntero o de la referencia.

Redefinición de una función virtual

Una función virtual en una clase base continúa siendo virtual cuando es heredada. Una clase derivada puede disponer de su propia versión de la función virtual o asumirla tal cual. Cuando la clase derivada provee de su propia versión, no será necesario volver a especificar la palabra clave virtual. Una clase derivada también puede contener sus propias funciones virtuales; es decir, funciones virtuales propias y no heredadas de sus clases base.
Para redefinir una función virtual en una clase derivada, dicha función debe tener el mismo nombre, número de tipos de argumentos y mismo tipo del valor retornado que la definida en la clase base. Si la redefinición difiere en el tipo del valor retornado, se producirá un error. Por otra parte, si difiere en los argumentos, la función será tratada como una función sobrecargada. Cuando en una clase base derivada se redefine una función de una clase base, se oculta la función de la clase base y todas las sobrecargadas de la misma en la clase base.

Constructores y destructores virtuales

C++ no admite constructores virtuales por tanto si un constructor llama a una función virtual, ésta corresponderá a la clase base, ya que todavía no se ha creado el objeto de la clase derivada.
Si una clase base y sus derivadas tienen definidos sus destructores. Cuando durante la ejecución se sale fuera del ámbito del objeto de una clase derivada, se llamará primero al destructor de esa clase derivada y después al destructor de su clase base.
Pero si el objeto ha sido creado dinámicamente y está referenciado por un puntero a la clase base, surge un problema, pues cuando se aplique el operador delete, el compilador llamará al destructor de la base aunque el objeto referenciado sea de una clase derivada. Para solucionar esto se debe declarar un destructor virtual,  pero esto hace que todos los destructores de todas las clases derivadas sean virtuales, aunque no compartan el mismo nombre que el destructor de la clase base. De esta forma, cuando se aplica delete a un puntero de la clase base, este llamará al destructor perteneciente  a la clase del objeto apuntado.
Una buena práctica será colocar un destructor virtual a una clase que posea funciones virtuales, aunque este destructor no haga nada. El motivo de esto es que una clase derivada puede requerir un destructor; por ejemplo, si se deriva una clase de una clase base que define un destructor para ella. Colocando un destructor virtual en la clase base, nos aseguramos de que el destructor de la clase derivada será llamado cuando se necesite.

Cómo se implementan las funciones virtuales

El compilador no puede identificar durante la compilación la función que va a ser llamada por una sentencia como volumen[i]->Visualizar( ), ya que podría ser cualquiera de varias funciones diferentes. Esto pasa si la función Visualizar se declara virtual en la clase, esto hace que también sea virtual en todas las clases derivadas donde esté definida.
El compilador evalúa la sentencia en tiempo de ejecución; es decir, cuando puede indicarle a qué tipo de objeto apunta Volumen[i]. Esto se conoce como asociación dinámica o asociación retrasada.
En OOP decimos que un mensaje dirigido a un objeto se asocia con un método. Cuando la asociación se hace durante la compilación, se denomina asociación estática. Y cuando se hace durante la ejecución se llama asociación dinámica. La asociación dinámica sucede si el método que se asocia está definido como virtual.

El compilador utilizará la asociación dinámica cuando no se puede utilizar una asociación estática, como ocurre a continuación:
void CBiblioteca::VisualizarVol( )
{
if (NVols > 0)
for ( int i = 0; i < NVols; i ++)
{
Volumen[i] -> Visualizar( );
cout << endl;
}
}
La asociación dinámica en C++ se implementa a través de una tabla de funciones virtuales o v-table. Dicha tabla está formada por un array de punteros a funciones que el compilador asocia a cada una de las clases que contienen una o más funciones virtuales.
La v-table contiene un puntero a una función por cada una de las funciones virtuales de la clase.

Clases abstractas y funciones virtuales puras

En este blog ya se habló de clases abstractas en VB.Net como se dijo, una clase abstracta es una clase que puede utilizarse solamente como clase base para otras clases. Se trata de disponer de un mecanismo que soporte la implementación de un concepto general, como figura, del cual utilizaremos variantes concretas, cómo círculo, cuadrado y triángulo o como se explicó en .net con las piezas de ajedrez. Pensando en estas ideas,abstractas no se pueden crear objetos de una clase abstracta; lo que crearemos serán objetos de sus clase derivadas.
Una clase para ser abstracta, necesita tener al menos una función virtual pura. Por ejemplo.
class CPieza             // clase abstracta
{
//...
virtual void Visualizar( ) const = 0;   //función virtual pura
//...
};

Una función virtual pura no requiere definición; es decir, no es necesario escribir el cuerpo de la función. El propósito de una declaración en la clase base es proveer a las clases derivadas de una interfaz polimórfica. Una clase derivada debe proveer una redefinición de la función virtual. Si no se hace, heredará la función virtual pura y se convertirá en una clase abstracta, pero esto no permitirá declarar objetos de la misma. Aunque visualizar se ha declarado constante (const) esto no quiere decir que sea necesario. Si se añade const, a las declaraciones y definiciones en las clases derivadas se tiene que añadir también const. No se pueden crear objetos de una clase abstracta. Si se intenta, el compilador muestra un error. Esta restricción es para prevenir cualquier llamada de un objeto a la función virtual pura.
CPieza obj; // error, clase abstracta.
Es posible declarar punteros y referencias a una clase  abstracta, pero una clase abstracta no se puede utilizar como tipo para un argumento ni como tipo retornado por una función por una función, ni en una conversión cast. Por ejemplo.
CPieza *p;             // correcto
CPieza &fn( CPieza &); // correcto

Clases virtuales

La jerarquía de clases ocasiona un problema por la derivación múltiple, la clase derivada heredará dos veces los miembros de la clase base. Por tanto cuando se crea un objeto de la clase derivada, éste contendrá dos sub objetos de la clase base. Tener dos sub objetos además de un derroche de espacio, hace que el compilador no sepa cual utilizar y genera un error.
Para asegurarnos de que sólo se utiliza un sub objeto de la "clase base indirecta". Es necesario declarar la clase base común virtual cuando se está derivando en las clases base directas.
class CBase    // clase base
{
// lista de miembros
};
class CDerivada1  : virtual public CBase
{
// lista de miembros
};
class CDerivara2  : virtual public CBase
{
// lista de miembros
};
class CDerivada12  : public CDerivada1, public CDerivada2

{
// lista de miembros
};
La palabra clave virtual, cuando se están derivando las  clases bases directas CDerivada1 y CDerivada2, asegura que el compilador solamente pasará a la clase CDerivada12 una copia de CBase.
Los constructores de una clase base virtual serán siempre llamados antes que los constructores de las clases no virtuales, y los destructores son siempre serán llamados en el orden inverso.

No hay comentarios:

Publicar un comentario