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(
);
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