14: Herencia y Composición

Tabla de contenidos

14.1. Sintaxis de la composición
14.2. Sintaxis de la herencia
14.3. Lista de inicializadores de un constructor
14.4. Ocultación de nombres
14.5. Funciones que no heredan automáticamente
14.6. Protected
14.7. Herencia y sobrecarga de operadores
14.8. Herencia múltiple
14.9. Desarrollo incremental
14.10. Upcasting
14.11. Resumen
14.12. Ejercicios

Una de las características más importantes acerca de C++ es la reutilización de código. Pero para ser revolucionario, necesita ser capaz de hacer algo más que copiar código y modificarlo.

Este es un enfoque de C y no fue demasiado bien. Como en la mayoría de los casos en C++, la solución gira alrededor de la clase. Se reutiliza código creando nuevas clases, pero en vez de crearlas desde la nada, utilizará clases existentes que alguien ha realizado y comprobado que funcionan correctamente.

La clave consiste en utilizar estas clases sin modificarlas. En este capítulo, aprenderá los dos modos de hacerlo. El primero es bastante directo: simplemente cree objetos de la clase existente dentro de la nueva clase. A esto se le llama composición porqué la nueva clase esta compuesta por objetos de clases ya existentes.

La segunda forma es mucho más sutil. Crear la nueva clase como un tipo de una clase existente. Literalmente se toma la forma de la clase existente y se añade código, pero sin modificar la clase ya existente. A este hecho mágico se le llama herencia, y la mayoría del trabajo es realizado por el compilador. La herencia es uno de los pilares de la programación orientada a objetos y tiene extensiones adicionales que serán exploradas en el capítulo 15.

Esto es, resulta que gran parte de la sintaxis y el comportamiento son similares tanto en la composición como en la herencia (lo cual tiene sentido; ambas son dos formas de crear nuevos tipos utilizando tipos ya existentes). En este capítulo, aprenderá acerca de los mecanismos para la reutilización de código.

14.1. Sintaxis de la composición

Realmente, ha utilizado la composición a lo largo de la creación de una clase. Ha estado construyendo clases principalmente con tipos predefinidos (y en ocasiones cadenas). Por esto, resulta fácil usar la composición con tipos definidos por el usuario.

Considere la siguiente clase:

//: C14:Useful.h
// A class to reuse
#ifndef USEFUL_H
#define USEFUL_H

class X {
  int i;
public:
  X() { i = 0; }
  void set(int ii) { i = ii; }
  int read() const { return i; }
  int permute() { return i = i * 47; }
};
#endif // USEFUL_H ///:~

Listado 14.1. C14/Useful.h


En esta clase los miembros son privados, y entonces, es completamente seguro declarar un objeto del tipo X público en la nueva clase, y por ello, permitir una interfaz directa:

//: C14:Composition.cpp
// Reuse code with composition
#include "Useful.h"

class Y {
  int i;
public:
  X x; // Embedded object
  Y() { i = 0; }
  void f(int ii) { i = ii; }
  int g() const { return i; }
};

int main() {
  Y y;
  y.f(47);
  y.x.set(37); // Access the embedded object
} ///:~

Listado 14.2. C14/Composition.cpp


Para acceder a las funciones miembro alojadas en el objeto (referido como subobjeto) simplemente requiere otra selección del miembro.

Es habitual hacer privado el objeto alojado, y por ello, formar parte de la capa de implementación (lo que significa que es posible cambiar la implementación si se desea). La interfaz de funciones de la nueva clase implica el uso del objeto alojado, pero no necesariamente imita a la interfaz del objeto.

//: C14:Composition2.cpp
// Private embedded objects
#include "Useful.h"

class Y {
  int i;
  X x; // Embedded object
public:
  Y() { i = 0; }
  void f(int ii) { i = ii; x.set(ii); }
  int g() const { return i * x.read(); }
  void permute() { x.permute(); }
};

int main() {
  Y y;
  y.f(47);
  y.permute();
} ///:~

Listado 14.3. C14/Composition2.cpp


Aquí, la función permute() se ha añadido a la interfaz de la clase, pero el resto funciones de X son utilizadas dentro de los miembros de Y.