class A {
private:
int pri;
protected:
int pro;
public:
int pub;
}
class B: public A {
int foo() {
this->pro = 0;
this->pub = 0;
this->priv = 0; // NO SE PUEDE
}
}
B b;
b.pub = 0;
b.pro = 0;
b.priv = 0;
class C: protected A {
// Restringimos el acceso a los atributos de la clase base
int p() {
this->pub = 0;
this->pro = 0;
this->priv = X; // no se puede acceder
}
}
- Filas: atributos - métodos
- Columnas: tipo de herencia
| | public | protected | private | |---------------------------------------------| | public | PUBLIC | PROTECTED | PRIVATE | | protected | PROTECTED | PROTECTED | PRIVATE | | private | - | - | - |
- En caso de que las clases hereden de varias clases que tengan métodos con nombres similares, implica que hay que desambiguar y definir explícitamente a qué método queremos llamar.
- En caso de que las clases sobre las que se hereda hereden de una misma clase, se duplican las variables, dado que cada clase genera su propio espacio de variables.
- A hereda de base: Genera su propio espacio de variables (con las properties de BASE)
- B hereda de base: Genera su propio espacio de variables (con las properties de BASE)
- C hereda de A y B: En su espacio de variables tiene las variables de A y de B, o sea, tiene dos veces las properties de BASE.
Esto se llama herencia diamante.
class Radio: public virtual Transmitter, public virtual Receiver {
virtual void m() {
Transmitter::m();
}
}
Hasta ahora manejamos los errores utilizando un código de error para comunicar al usuario que la función ha fallado en su ejecución. Hay algunos problemas al respecto:
- Si se realizan operaciones tales como reserva de memoria o apertura de archivos, hay que efectuar la operación inversa por cada operación exitosa
- El código se complejiza
Se pueden lanzar excepciones (que pueden ser tranquilamente números enteros) y catchear excepciones, pero tranquilamente puede terminar siendo una seguidilla de ifs.
- Usando RAII se puede controlar errores en los constructores. Si pincha algún constructor, se lanza la excepción y se disparan todos los destructores de los objetos creados hasta el momento, no hay necesidad de catchear y encargarse de destruir las cosas manualmente.
- Un buen uso de RAII nos permite programar el camino feliz sin tanto chequeo "a mano".
- Si el constructor falla NO SE LLAMA AL DESTRUCTOR. Para eso hay que usar RAII sobre RAII (ver el ejemplo de
DoubleBuffer
y su member initalization list) std::uniq_ptr<T>
: punteros RAII. referencia.- NO LANZAR EXCEPCIONES EN UN DESTRUCTOR: Si se lanza una excepcion y se dispara el destructor, este puede lanzar otra excepción y a c++ no le gusta esto...
Hay distintos niveles de seguridad para mantener los objetos en estado válido al momento de ser modificados:
- Unsafe: Si un método que modifica deja el objeto en estado inválido, se tira una excepción pero el objeto queda inválido.
- Exception safe weak: Si un método que modifica el objeto está por dejarlo en estado inválido, se lanza una excepción justo antes de invalidarlo. Esto puede modificar "a medias" un objeto.
- Exception safe strong: Si se modifica el objeto, se lanza una excepción que preserva el estado original.
- Lanzar excepciones con mensajes que tengan la mayor cantidad de información para que el debugging sea más sencillo.