You are on page 1of 22

Curso de programacin avanzada en

C++
Normas de estilo en C++
Igual que escribir en un idioma tiene una serie de normas de
estilao, que, si se cumplen, hacen el texto ms comprensible y
elegante, igual los lenguajes de programacin tienen una serie
de normas de estilo, para hacer su cdigo ms elegante,
comprensible e incluso fcil de depurar. En C++, las normas de
estilo parten del diseo correcto de las clases, de la utilizacin
correcta de la herencia y de la encapsulacin, y del
aprovechamiento de todas las capacidades de C++.
Simultneamente, veremos como los conceptos fundamentales
de la programacin orientada a objetos, tales como la herencia y
encapsulacin se implementan en C++.
Forma cannica
La forma cannica es la forma ortodoxa de declarar una clase en
C++. Permite evitar problemas de programacin, y permite que
una clase declarada de esta forma se pueda usar de la misma
manera que cualquier tipo tradicional de C.
En la forma cannica, evidentemente, tienen que entrar el
constructor y el destructor.
class miClase {
public:
/// Constructor
miClase() {};
/// Destructor
~miClase() {};
}

Habitualmente se cae en la tentacin de no declarar ni el


constructor ni el destructor si se utilizan los constructores por
defecto; de hecho, los compiladores generan automticamente el
cdigo para ellos; sin embargo, siempre es bueno hacerlo,
aunque sea slo para guardarles sitio, porque ms adelante
podra interesar cambiarlos por otro tipo de constructor y no
sabramos donde meterlos. Los comentarios tambin son
esenciales, aunque sean redundantes. En este caso, se usan la
convencion del doc++, que genera automticamente la
documentacin; en caso de que no se incluyeran los
comentarios, el usuario podra pensar mirando a la
documentacin que no existe el constructor, o que se nos ha
olvidado incluirlo. Otra razn por la que se debe incluir un
constructor vaco es para que se puedan declarar contenedores
de las STL que contengan ese tipo; algunos compiladores, tales
como el Visual C++, lo exige.
No solamente estos elementos son esenciales. El constructor de
copia tambin lo es. El constructor de copia, aunque no lo
parezca, se usa mucho. Por ejemplo, se est llamando al
constructor de copia cuando se hace
miClase zape;
miClase zipi=zape;

o cuando se llama a un procedimiento o funcin por valor,


en vez de por referencia (o usando punteros o referencias).
void unMetodo( miClase _zipi ) {
// hacer lo que sea con _zipi
}
miClase pantuflo;
unMetodo(pantuflo);

El constructor de copia tambin lo genera el compilador


por defecto, pero a veces puede que lo que haga ese
constructor generado no sea lo que nosotros pretendemos
que haga. Por ejemplo, si el objeto incluye punteros a
objetos asignados por el mismo, ese constructor generado
copiar los punteros, no a lo que apuntan (copia

superficial). Incluyendo el constructor de copia. la forma


cannica quedara as
class miClase {
public:
/// Constructor
miClase() {};
/// Constructor de copia
miClase( const miClase& _c ) {
// Inicializar las variables de instancia
}
/// Destructor
~miClase() {};
}

El objeto que se va a copiar se pasa como una referencia constante. El usar


referencias para pasar parmetros a procedimientos y funciones es una buena
costumbre: evita tener que enviar muchos bytes a la pila en caso de que se trate
de un objeto complejo; adems, la referencia tiene que ser constante como una
promesa de que no se le va a hacer nada al objeto dentro de esa funcin.
Todava la forma cannica est incompleta. Qu ocurre si
queremos copiar un objeto ya construido sobre otro ya
construido? Har falta el operador de asignacin. Vamos a por l
class miClase {
public:
/// Constructor
miClase() {};
/// Constructor de copia
miClase( const miClase& _c ) {
// Inicializar las variables de instancia
}

/// Operador de asignacin


const miClase& operator=( const miClase& _c ) {
// Comprobacin si no es uno mismo
if ( _c !=*this) {
// Copiar variables de instancia
}
return *this;
}
/// Destructor
~miClase() {};
}

El operador=devuelve una referencia al objeto para que se


puedan hacer cosas tales como
miClase zape, zipi, pantuflo;
zape=( zipi=pantuflo );

Pero para que no se pueda hacer (zipi=pantuflo)=zape la


referencia se hace constante. Dentro del operador de asignacin
se tiene que comprobar si se est asignando el propio objeto
sobre s mismo; sobre todo si hay que destruir y asignar memoria
dinmica, y luego copiarla.
En algunos casos puede que no se quiera usar alguno de estos
elementos, o que no tenga sentido. En tal caso, deberan
declararse protected o bien private.
En todo esto, dnde deberan ir las variables de instancia? No
en cualquier sitio, no. Aqu es donde entra la encapsulacin.
Encapsulacin de un objeto significa regular el acceso a su
interior a travs de un interface. Si ponemos tales variables en el
interface (es decir, la ponemos en la parte public o protected), la
regulacin del acceso se va claramente a tomar por saco. Si est
en la parte public, porque cualquiera puede cambiar su valor, y si

est en la parte protected, porque puede cambiarlo cualquier


objeto de la jerarqua de clases.
class miClase {
public:
/// Constructor
miClase():
repVariableInstancia1(),repVariableInstancia2(){};
/// Constructor de copia
miClase( const miClase& _c )
:
repVariableInstancia1( _c.repVariableInstancia1),
repVariableInstancia2( _c.repVariableInstancia2) {
// Inicializar las otras variables de
instancia
}
/// Operador de asignacin
const miClase& operator=( const miClase& _c ) {
// Comprobacin si no es uno mismo
if ( _c !=*this) {
// Copiar variables de instancia
}
return *this;
}
/// Destructor
~miClase() {};
private:
tipoVariable repVariableInstancia1;

otroTipoVariable repVariableInstancia2;
}

Aparte de introducir las variables de instancia, hemos introducido


su inicializacin en los dos constructores que tenemos a mano, y
lo hemos hecho usando la lista de inicializacin. Esta forma es
mucho ms eficiente, y evita la construccin de variables
temporales (sobre todo en el constructor de copia). Pero algunas
veces puede uno querer cambiar los valores de estas variables.
As no se puede! Bueno, en general, las clases deberan
disearse de forma que no se necesitara acceder o siquiera
saber el nombre de las variables de instancia (el ideal en
programacin dirigida a objetos es separar totalmente el interfaz
de la implementacin), pero si alguna clase derivada contumaz
quiere acceder a ellas, se deberan declarar funciones dentro del
interfaz protegido.
class miClase {
public:
/// Constructor
miClase() {};
/// Constructor de copia
miClase( const miClase& _c ) {
// Inicializar las variables de instancia
}
/// Operador de asignacin
const miClase& operator=( const miClase& _c ) {
// Comprobacin si no es uno mismo
if ( _c !=*this) {
// Copiar variables de instancia
}
return *this;
}

/// Destructor
~miClase() {};
protected:
tipoVariable& variableInstancia1() {
return repVariableInstancia1;
}
const tipoVariable& variableInstancia1() const
{
return repVariableInstancia1;
}
private:
tipoVariable repVariableInstancia1;
otroTipoVariable repVariableInstancia2;
}

El lector avezado podr pensar que estas dos declaraciones son


iguales; y efectivamente lo son. Para ser exactos, la funcin
variableInstancia1 est sobrecargada, es decir, que hace dos
funciones diferentes dependiendo del contexto en el que sea
invocada. Cul es en este caso el contexto? El contexto viene
determinado por el const delante y detrs de la segunda
declaracin. Ese const indica que, aparte de devolver una
referencia constante, es decir, que no se puede asignar un valor
a lo que devuelva esa funcin, no altera el contenido del objeto
(el segundo const), con lo cual podemos llamar a esta funcin
desde aquellas otras funciones a las que se les pase un objeto
const.
Nunca se deben devolver referencias o punteros no constantes a
las variables de instancia, ni siquiera en el interfaz protegido,
porque si no la encapsulacin se va a hacer pueta: cualquiera
podra asignarles un valor desde fuera, o alterar un puntero. Las
referencias o punteros que se devuelvan deben ser constantes, a

no ser que efectivamente queramos que le asignen valor desde


fuera, algo bastante poco aconsejable.
Ya tenemos una clase completa declarada; hay que fijarse en el
orden de la declaracin. Pensando en los clientes, sobre todo, la
parte pblica debe ir la primera, porque ah es donde mirarn los
programadores para ver qu es lo que pueden usar; otra
categora de programadores mirar a la parte protected, si le
interesa heredar de la clase; y los listillos, por ltimo, mirarn a la
parte privada pensando: "Hum, este nombre de variable no me
convence", o bien "Si pusiera esto en la parte pblica, me
ahorrara muchas cosas". Pues no!
Tambin hemos hecho algo que no tiene porqu sentarle bien a
todo el mundo: incluir la definicin de varias funciones al lado de
la declaracin, en el fichero de cabecera. Aunque algunas guas
de estilo lo indiquen as, en otros casos, como en casi todos los
ejemplos de las STL, aparecen como arriba, y en todo caso es
mucho ms cmodo.

El interfaz de una clase


El interfaz de una clase es lo ms importante de la misma, casi
me atrevera a decir que ms importante que su implementacin.
Cuando se piensa en un programa, se debe pensar en los
objetos que hay en el mismo y en sus interfaces; la
implementacin vendr luego. Incluso, con un interfaz bien
diseado, la mitad de la implementacin est hecha. Por eso es
tan importante.
Como ya hemos visto, el interfaz tiene dos partes: la pblica y la
protegida. La pblica es lo que la clase ofrece a los clientes, y la
protegida es la que le ofrece a los clientes y los descendientes.
La regla que hay que seguir es que el interfaz debe ser mnimo y
completo, mnimo porque debe incluir slo lo necesario, y
completo, porque debe incluir todas las operaciones necesarias
para que se pueda usar la clase como nosotros pretendemos (y
slo como nosotros pretendemos: no olvidemos que si algo se

puede hacer, se har). Una clase con 2 o 3 funciones aparte de


las de la forma cannica es el ideal.Esta regla se aplica a cada
uno de los interfaces. En la parte privada, uno puede meter todo
lo que le d la gana, que para eso es privada.
Algo que hay que tener en cuenta aqu es que las
clases friend forman tambin parte del interfaz de una clase,
puesto que pueden acceder a sus variables privadas de
instancia. Por ello, caben dos opciones. La primera es no usar
nunca los friends: meter todo lo necesario directamente dentro
de la declaracin de la clase. Segunda, usarlos aplicando la
norma anterior: mantener el interfaz mnimo, es decir, dejar en
las clases amigas alguna parte del interfaz que por sus
caractersticas deba estar aparte.
Las funciones constantes (const) se deben decidir desde el
principio del diseo. Si una funcin no altera ninguna variable de
instancia de un objeto, debe ser declarada constante. Estas
sern las nicas funciones que se pueden que invocar desde
variables declaradas como punteros o referencias constantes a
un objeto. As, adems, controlas claramente el acceso a tu
clase: slo aquellos mtodos declarados como no-constantes
alterarn al objeto, y tomaremos las medidas pertinentes.
void unaFuncion( const miClase&_param ) {
tipoVariable
unaVar=_param.variableInstancia1(); // Correcto
tipoVariable otraVar;
_param.variableInstancia1()=otraVar; // Error
de compilacin
}

En este caso, en la primera invocacin del


mtodo variableInstancia1() se est llamando a la versin
constante del mismo, y por lo tanto no hay problema; en este
caso se deduce que es la versin constante por el contexto: no
se est haciendo nada para alterar el contenido del objeto. Sin
embargo, en el segundo caso, indicado en rojo, se est llamando
al mtodo no-constante, el que devuelve una referencia a

repVariableInstancia1, lo cual el compilador deduce una vez ms


por el contexto: se le est tratando de asignar un valor. Como
_param est declarado como una referencia constante, el
compilador detecta un error. Los mtodos constantes, a su vez,
son los nicos que se pueden llamar desde otros mtodos
constantes.
Ejercicio: disear una clase Polinomio
que permita acceder a cada uno de sus
coeficientes, imprimirlo como polinomio,
y hacer alguna operacin aritmtica tal
como suma y resta.

De tal palo, tal astilla


Ya hemos visto como se construye una clase de forma que la
encapsulacin se respete, y el interfaz est bien diseado; pero
el otro aspecto que hace de C++ un lenguaje orientado a objetos
(o dirigido a objetos) es la herencia: la posibilidad de reusar las
clases ya definidas, en todo o en parte, para hacer nuevas
clases. Al grupo de todas las clases que descienden de una
clase principal se le denomina jerarqua de clases. A la clase
madre o padre se le puede llamar tambin superclase o clase
base, mientras que a las clases descencientes se les llama
tambin subclases o clases derivadas. Por tanto, una subclase
hereda de una superclase, y ambas juntas (y todas las dems)
constituyen una jerarqua de clases.
En general, la herencia en C++ expresa la relacin es-un. Una
subclase es-una superclase, pero con algunas diferencias. Lo
que no queda claro es que es esa relacin. En otros lenguajes
est un poco ms claro. Por ejemplo, en Objective-C hay dos
tipos de herencia: herencia de cdigo, y herencia de interfaz
(cuando un objeto implementa un interfaz que se ha declarado
anteriormente). En Java, e incluso en Visual Basic, se pueden
declarar interfaces como objetos de pleno derecho, y la herencia
de tales interfaces (habitualmente denominada implementacin,
va por un lado diferente que la herencia de cdigo. En C++, la
herencia es una mezcla de las dos: se hereda a veces interfaces,
a veces implementacin, a veces ambos. En el siguiente
ejemplo:

class miClase {
// Dejar todo lo dems, y aadir alguna funcin
/// Mtodo puesto solo para heredar
void metodo( void ) const; {
// Una implementacin del mtodo
}
}
/// Esta subclase es una subclase tonta que no hace
nada en realidad
class miSubclase: public miClase {
/// Mtodo puesto solo para heredar
void metodo( void ) const; {
}
}

Qu est heredando, en realidad, miSubclase de miClase?


Algunas cosas. Est heredando el interfaz, puesto que tiene una
funcin con el mismo nombre que la clase base, pero no la
implementacin, puesto que est redefiniendo el codigo de un
mtodo (apropiadamente llamado metodo) definido en la clase
base. Aparte de eso, hereda bien poco: ninguno de los
constructores ni destructores. S toma de la clase base parte del
cdigo: las funciones protegidas y las variables de instancia,
aunque no puede acceder a stas porque estn declaradas
como privadas.
Sin embargo, esta declaracin puede causar problemas a la hora
de trabajar con punteros o referencias a objetos de la clase:
miClase& refZipi;
miSubclase& refZape;
miSubclase zipi;
refZipi = zipi;
refZipi.metodo();

// Llama a miClase::metodo

refZape = zipi;
refZape.metodo();

// Llama a miSublase::metodo

Con punteros sucederia algo similar, pero los punteros causaran


an ms problemas a la hora de destrur el objeto al que apuntan:
miClase* pZipi = new miSubclase();

// Conversion

automtica de puntero de clase derivada a clase


base
delete pZipi; // Correcto, pero llamara a
miClase::~miClase()

Para resolver todos estos problemas se inventaron las


funciones virtuales. En C++, una funcn declarada como virtual
se asocia al cdigo en concreto que va a ejecutar en tiempo de
ejecucin, no en tiempo de compilacin como ocurre con el resto
de las funciones. Para ello, cada objeto perteneciente a una
clase con funciones virtuales contiene una estructura de datos
denominada vptr que contiene punteros a todas las
implementaciones de funciones virtuales a lo largo y ancho de la
jerarqua de clases. Todo esto, por supuesto, supone un
aumento del espacio de almacenamiento para los objetos de la
clase; y adems supone el gasto de un direccionamiento
indirecto adicional cada vez que se llama a un mtodo virtual.
Algo a tener en cuenta, si la velocidad y el espacio son
esenciales. Que no deberan serlo: para solucionar esos
problemas est el hardware.
Usando funciones virtuales, y declarando la subclase tambin en
forma cannica, las dos clases anteriores quedaran as:
class miClase {
// eliminados los comentarios y cdigo para dejarlo
todo ms claro
public:
miClase() {};
miClase( const miClase& _c );

const miClase& operator=( const miClase& _c );


virtual ~miClase() {};
virtual void metodo( void ) const;
protected:
tipoVariable& variableInstancia1();
const tipoVariable& variableInstancia1()
const
private:
tipoVariable repVariableInstancia1;
otroTipoVariable repVariableInstancia2;
}
/// Esta subclase es una subclase tonta que no hace
nada en realidad
class miSubclase: public miClase {
public:
miSubclase(): miClase(), varInstSubclase() {};
miSubclase( const miSubclase& _c )
:miClase( _c ),
varInstSubclase( _c.varInstSubclase) {};
const miClase& operator=( const miClase& _c );
virtual ~miClase() {};
virtual void metodo( void ) const;
// resto del codigo
private:
tipoVar varInstSubclase;
}

En este ejemplo, aparte de declarar el destructor de la clase


derivada y el mtodo redefinido como virtual, se han aadido una
serie de novedades que indican la forma cannica de inicializar

la clase base y las variables de instancia: se deben inicializar por


ese orden, y si es posible, usando la lista de inicializacin (es
decir, tal como se ha hecho arriba: dos puntos detrs del nombre
del constructor, y las variables y superclases a inicializar
separadas por comas). El usar la lista de inicializacin permite
que se genere cdigo ms eficiente, y permite detectar en tiempo
de compilacin los errores en el orden de inicializacin de las
variables.
Adems, hemos convertido en virtuales todos los destructores y
el metodo. De hecho, todas las funciones virtuales de una
jerarqua de clase constituyen el interfaz de la jerarqua, y no el
interfaz de una clase en particular dentro de la jerarqua; por ello
hay que prestar especial cuidado al diseo de este interfaz, tanta
o ms que al diseo del interfaz de cada clase. Aunque es casi
imposible disear una jerarqua de clases que solamente tenga
funciones virtuales, se puede intentar, concentrando todas las
diferencias entre miembros de la jerarqua en el constructor.
No se debe usar la herencia para expresar relaciones tales
como es parte de: en tal caso debera usarse herencia privada;
ni tampoco la relacin usa-un, en cuyo caso deber ponerse el
objeto usado como variable de instancia. Por ejemplo, un
coche es-un vehculo, y un motor es-parte-de un coche; por lo
tanto, la clase coche debera heredar de la clase vehlo y debera
contener una variable de instance de la clase motor.
Aunque en este cuadro aparezcan las dos clases juntas, lo ms
conveniente es que vayan separadas cada una en su fichero; y si
es posible, el interfaz separado tambin de la implementacin,
cada uno en su fichero (incluso aunque se trate de templates).
Lo ms universal es nombrar los ficheros de cabecera con la
extensin .h y los que contienen la implementacin con .cpp; la
mayora de los compiladores entienden estas extensiones,
aunque para que funcionen correctamente los Makefiles en Unix
deber aadirse una orden. Los ficheros tendrn el mismo
nombre de la clase, incluso teniendo en cuenta maysculas y
minsculas; los tiempo en que tenia uno que poner nombres de
ficheros con 8 caracteres estn afortunadamente periclitados, y
cualquier sistema operativo decente admite nombres largos
(aunque algunos supuestamente decentes, como Windows NT,
todava confundan maysculas y minsculas.

Las funciones virtuales sirven para una cosa ms: dado que
definen el interfaz de una clase, existen en C++ las funciones
virtuales puras que slo definen un interfaz, sin aportar
(habitualmente) ningn cdigo, y obligando adems a las clases
derivadas a implementar ese cdigo. Un caso clsico de estas
funciones o mtodos virtuales puros es el mtodo que imprime la
clase a un canal de salida:
class miClase {
// eliminados los comentarios y cdigo para dejarlo
todo ms claro
public:
miClase() {};
miClase( const miClase& _c );
const miClase& operator=( const miClase& _c );
virtual ~miClase() {};
virtual void metodo( void ) const;
virtual void printSelf(
ostream& _o) const = 0; // mtodo virtual puro
protected:
tipoVariable& variableInstancia1();
const tipoVariable& variableInstancia1()
const
private:
tipoVariable repVariableInstancia1;
otroTipoVariable repVariableInstancia2;
}
/// Esta subclase es una subclase tonta que no hace
nada en realidad
class miSubclase: public miClase {
public:
miSubclase(): miClase(), varInstSubclase() {};

miSubclase( const miSubclase& _c )


:miClase( _c ),
varInstSubclase( _c.varInstSubclase) {};
const miClase& operator=( const miClase& _c );
virtual ~miClase() {};
virtual void metodo( void ) const;
virtual void printSelf(
ostream& _o) const; // El cdigo ira en otro lado
private:
tipoVar varInstSubclase;
}

Los mtodos virtuales puros convierten a nuestra archiconocida


clase base en una clase base abstracta (CBA). En cierto modo,
una CBA declara un interfaz solamente, y deja a las clases
derivadas la tarea de definir implementaciones. Este hecho se
puede usar en conjuncin con los templates, para definir qu tipo
de clases pueden instanciar un template determinado.
Habitualmente, cuando se escribe un template se debe de
especificar claramente cual es el interfaz que debe tener la clase
para que sea posible instanciarlo; sin embargo, esto da lugar a
descripciones ms bien largas y puede ser problemtico. En vez
de eso, es mejor declarar una CBA que tenga ese interfaz, e
indicar simplemente que ese template se puede instanciar
solamente con clases que desciendan de esa CBA. Por ejemplo
/** Este comentario sigue la sintaxis del doc++
Esta funcion solo puede instanciarse con aquellos
objetos que
tengan los mtodos a() y b( foo& ) */
template<class T> void baz<T>( const T& _t){
_t.a();// y ms cosas
foo miFoo;
_t.b( miFoo );

}
// Pero es ms fcil hacerlo as
class miCBA{
public:
void a() = 0;
void b( foo& ) = 0;
}
/** T debe descender de la clase miCBA */
template<class T> void baz<T>( const T& _t){
_t.a();// y ms cosas
foo miFoo;
_t.b( miFoo );
}

Ms formas de construir
Aunque un constructor propio, con ms o menos sofisticacin es
la forma habitual de construir una clase, hay clases que no
tienen porqu saber como construirse. O, en algunos casos, toda
la complejidad del constructor de un objeto hay que encapsularla
en otro objeto, que sabr como construirlo. Por ejemplo, en
entornos de aplicaciones tales como
COM
(Common Object Model, de Microsoft), es necesario disear factoras para todos los
objetos, de forma que los clientes que los usen no tengan que saber como construir cada
objeto especfico.

En algunos casos tambin, objetos complejos, compuestos de


referencias y punteros a otros objetos, ni siquiera pueden
construirse a s mismos, porque no pueden asignar valores a las
referencias que usan; tampoco deberan asignar memoria a sus
propios punteros, pues en ese caso no quedara claro quin
tendra que desasignarlo, si el propio objeto o quien ha creado
los punteros. En esta seccin incluiremos una serie de patrones
para construir objetos de diferente forma.

En algunos casos es necesario tener un constructor de copia


virtual. Ninguno de los constructores se heredan, ni siquiera el
operador de asignacin, pero cuando estamos usando
referencias o punteros a una clase base, y se quiere copiar el
objeto, no se pueden usar los constructores de copia, que
copiarn el objeto de la clase base o bien simplemente el
puntero. Se puede incluir el constructor de copia virtual de la
forma siguiente
class miClase {
// eliminados los comentarios y cdigo para dejarlo
todo ms claro
public:
miClase() {};
miClase( const miClase& _c );
const miClase& operator=( const miClase& _c );
virtual ~miClase() {};
virtual void metodo( void ) const;
/** Ctor virtual de copia; devuelve un puntero
constante para que no
se pueda usar como operador de asignacin. Sin
implemtacin:
Un objeto que no se puede instanciar no sepuede
copiar
*/
virtual const miClase* clone() const = 0;
virtual void printSelf(
ostream& _o) const = 0;
protected:
tipoVariable& variableInstancia1();
const tipoVariable& variableInstancia1()
const

private:
tipoVariable repVariableInstancia1;
otroTipoVariable repVariableInstancia2;
}
class miSubclase: public miClase {
public:
miSubclase(): miClase(), varInstSubclase() {};
miSubclase( const miSubclase& _c )
:miClase( _c ),
varInstSubclase( _c.varInstSubclase) {};
const miClase& operator=( const miClase& _c );
virtual ~miClase() {};
virtual void metodo( void ) const;
virtual const miClase*
clone() const; {
return new
miSubclase(*this)
}
virtual void printSelf(
ostream& _o) const;
private:
tipoVar varInstSubclase;
}

La funcin cloneactuara como un constructor de copia virtual,


produciendo nuevas copias del objeto dondequiera que est en
la jerarqua de clases. Por ejemplo, se pueden hacer cosas as:
class miClase {
// eliminados los comentarios y cdigo para dejarlo
todo ms claro

public:
miClase() {};
miClase( const miClase& _c );
const miClase& operator=( const miClase& _c );
virtual ~miClase() {};
virtual void metodo( void ) const;
/** Ctor virtual de copia; devuelve un puntero
constante para que no
se pueda usar como operador de asignacin. Sin
implemtacin:
Un objeto que no se puede instanciar no sepuede
copiar
*/
virtual const miClase* clone() const = 0;
virtual void printSelf(
ostream& _o) const = 0;
protected:
tipoVariable& variableInstancia1();
const tipoVariable& variableInstancia1()
const
private:
tipoVariable repVariableInstancia1;
otroTipoVariable repVariableInstancia2;
}
class miSubclase: public miClase {
public:
miSubclase(): miClase(), varInstSubclase() {};
miSubclase( const miSubclase& _c )

:miClase( _c ),
varInstSubclase( _c.varInstSubclase) {};
const miClase& operator=( const miClase& _c );
virtual ~miClase() {};
virtual void metodo( void ) const;
virtual const miClase*
clone() const; {
return new
miSubclase(*this)
}
virtual void printSelf(
ostream& _o) const;
private:
tipoVar varInstSubclase;
}

Lo que permite usar esta funcin de la forma siguiente:


miClase& pMiClase;
miClase zipi;
miSubclase zape;
pMiClase = zipi;
miClase* nuevoZipi = pMiClase.clone(); // Llama a
miClase::clone()
pMiClase = zape;
miClase* nuevoZape = pMiClase.clone(); // Llama a
miSubclase::clone()

La ltima versin del C++ permite incluso declarar la funcin


clone as:
virtual const miSubclase* clone() const; {

return new miSubclase(*this)


}

Es decir, aquellas funciones que devuelvan punteros a la clase


base pueden ser sustituidas en la clase derivada por funciones
que devuelvan punteros a la misma. Lo mismo ocurre con las
referencias.

You might also like