Clases anidadas e internas
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 38 de 104.
Una clase anidada es una clase definida dentro de otra clase. Esta técnica ayuda a organizar el código agrupando tipos relacionados y puede mejorar la encapsulación al ocultar los detalles de implementación.
class LinkedList {
public:
class Node { // Clase anidada
int data;
Node* next;
public:
Node(int d) : data(d), next(nullptr) {}
friend class LinkedList;
};
private:
Node* head;
public:
LinkedList() : head(nullptr) {}
};La clase anidada Node está estrechamente acoplada a LinkedList; solo tiene sentido en ese contexto. Al anidarla, señalamos esta relación y evitamos contaminar el espacio de nombres global con un nombre genérico como "Node".
Las clases anidadas siguen las mismas reglas de acceso que has aprendido. Una clase anidada declarada en la sección privada solo es accesible dentro de la clase externa.
Cuando se declara como público, el código externo puede usarlo con el operador de ámbito:
LinkedList::Node node(42); // Solo si Node es publicUn detalle importante sobre la dirección: una class anidada es un miembro de la class contenedora y tiene los mismos derechos de acceso que cualquier otro miembro, por lo que puede acceder a los miembros privados de la class contenedora sin una declaración friend. Sin embargo, lo contrario no es automático: la class contenedora no tiene acceso a los miembros privados de la class anidada.
Por eso Node declara friend class LinkedList; en el ejemplo anterior: la declaración friend otorga a la LinkedList que la contiene acceso a los miembros privados de Node, la única dirección que no es automática.
Desafío
FácilConstruyamos un sistema sencillo de gestión de tareas en el que las tareas estén organizadas dentro de un proyecto. Este es un escenario perfecto para las clases anidadas: una Task solo tiene sentido en el contexto de una Project, así que la anidaremos dentro para mantener nuestro código organizado y evitar contaminar el espacio de nombres global.
Crearás dos archivos para organizar tu código:
Project.h: Define una claseProjectque contiene una claseTaskanidada.Tu clase
Taskanidada debe declararse en la sección public deProjectpara que se pueda acceder a ella desde fuera. Debe almacenar un nombre de tarea (cadena) y un estado de finalización (booleano). Asígnale un constructor que reciba el nombre y establezca la tarea como incompleta de forma predeterminada. Incluye los métodoscomplete()para marcarla como completada,isComplete()para comprobar su estado ygetName()para recuperar el nombre.La propia clase
Projectdebe almacenar un nombre de proyecto (cadena) y una colección de punteros aTask. Proporciona un constructor que reciba el nombre del proyecto, un métodoaddTask(const std::string& taskName)que cree una tarea nueva y la añada al proyecto, un métodocompleteTask(const std::string& taskName)que busque y complete una tarea por nombre, y un métododisplayStatus()que muestre el nombre del proyecto seguido del estado de cada tarea.Como
Taskestá anidada dentro deProject, la claseProjectpuede trabajar directamente con las tareas. No olvides liberar las tareas asignadas dinámicamente en el destructor.main.cpp: Une todos los elementos leyendo un nombre de proyecto y dos nombres de tarea de la entrada (tres líneas en total). Crea unProjectcon el nombre proporcionado, añade ambas tareas y después muestra el estado inicial. Completa la primera tarea y vuelve a mostrar el estado para ver el cambio.El formato de salida de
displayStatus()debe ser:Project: <project_name> - <task_name>: [incomplete] - <task_name>: [complete]Usa
[complete]o[incomplete]según el estado de cada tarea.
Este desafío demuestra cómo las clases anidadas ayudan a organizar tipos relacionados: la clase Task está estrechamente vinculada a Project y no tendría sentido por sí sola. Al anidarla, indicamos claramente esta relación en la estructura de nuestro código.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "Project.h"
using namespace std;
int main() {
// Leer entrada
string projectName;
string task1Name;
string task2Name;
getline(cin, projectName);
getline(cin, task1Name);
getline(cin, task2Name);
// TODO: Crea un Project con el nombre dado
// TODO: Añade ambas tareas al proyecto
// TODO: Muestra el estado inicial
// TODO: Completa la primera tarea
// TODO: Muestra el estado de nuevo para mostrar el cambio
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genéricoPractica por tu cuenta: Compilador de C++ online