Práctica 4
A continuación se encuentra el enunciado de la práctica 4. Lee cuidadosamente el enunciado y sigue las instrucciones. Se recomienda consultar el apartado de Teoría para tratar de resolver dudas de concepto.
1 Cambios
1.1 Versión 1.0.0
- Enunciado original.
2 Archivos de partida.
- Aquí se encuentran los archivos de partida. Descárgalos y utilizalos como base para resolver la práctica.
- Fíjate en la estructura de directorios. Cuando hagas la entrega del archivo comprimido (
irp2-p4.tgz
), tu código tendrá que estar en la carpetairp2-p4/src
, en el mismo sitio donde se encuentran los archivos de cabecera del código de partida.
- Fíjate en la estructura de directorios. Cuando hagas la entrega del archivo comprimido (
3 Objetivos
-
Aplicar los conceptos de Programación Orientada a Objetos vistos en clase:
- Herencia y clases abstractas.
- Clases genéricas con plantillas (
template
). - Espacios de nombres.
- Modularización del código.
- Señales (
boost::signals2
). - Redefinición de operadores.
-
También aprenderemos a separar en diferentes carpetas (
src
,bin
) el código fuente (archivos de cabecera y de implementación) del código objeto generado.- Para ello, fíjate en el archivo
Makefile
. Cuando utilices la herramientamake
, los archivos generados tras la compilación se guardarán en una carpeta llamadabin
. Esta carpeta no es necesario que exista previamente, se crea automáticamente en el caso de que no exista.
- Para ello, fíjate en el archivo
4 Enunciado
4.1 Introducción
La Estrella de la Muerte cuenta con un avanzado sistema de gestión. Eres el responsable del sistema de control de hangares de la Estrella de la Muerte. Tu misión es gestionar las flotas de naves de los hangares del Imperio (¡algunas incluso robadas de la Alianza Rebelde!).
Cada nave tiene un comportamiento específico (definido por el tipo de nave), un símbolo que la representa gráficamente, y una cantidad de energía. Tras ejecutar su misión, informa de su estado actual mediante señales.
La jerarquía de clases se divide en:
Ship
→ clase base abstracta para cualquier tipo de nave.ImperialShip
→ clase para naves del Imperio.RebelShip
→ clase para naves de la Alianza Rebelde.- Clases concretas como
TIEFighter
,StarDestroyer
oXWing
, representando los diferentes tipos de naves disponibles. Container
→ clase que representa un contenedor de elementos, que pueden ser cualquier cosa. Dispone de una serie de funciones para interactuar con estos elementos. Es una plantilla (template), y se implementará encontainer.tcc
. Fíjate que no es.cc
.Hangar
→ clase que representa un contenedor de naves.
Más adelante podrás ver una descripción de cada una y un diagrama UML con el esquema general de las clases y las relaciones entre ellas.
4.2 Clases
A continuación vamos a describir las diferentes clases que tenemos.
1. Ship
Atributos:
- char c: carácter que representa la nave
- bool in_mission: indica si la nave se encuentra en una misión, fuera del hangar.
- uint energy: energía de la nave
- mission_sig_t mission_sig: señal que se activará cuando una nave realice una misión, en la función
run_mission
(ver más abajo).
Funciones:
-
friend std::ostream &operator<<(std::ostream &os, const Ship& s);
- Operador de salida que añadirá al parámetro ostream una representación de la nave para mostrarla en consola. La representación será el carácter que esté guardado en el atributo
c
rodeado de corchetes. Por ejemplo, sic = 'x'
, entonces su representación será:[x]
.
- Operador de salida que añadirá al parámetro ostream una representación de la nave para mostrarla en consola. La representación será el carácter que esté guardado en el atributo
-
Ship(char c, uint energy);
- Constructor que inicializará sus atributos.
-
virtual ~Ship() = default;
- Destructor por defecto.
Métodos accesores (setter y getter) del atributo c
.
- void set_drawing_char(char c);
- char get_drawing_char() const;
Métodos accesores (setter y getter) del atributo in_mission
.
- void set_in_mission(bool in_mission);
- bool is_in_mission() const;
Métodos accesores (setter y getter) del atributo energy
.
-
uint get_energy() const;
-
void set_energy(uint energy);
-
void reduce_energy(uint energy);
- Reduce la energía en la cantidad especificada.
- Si se intenta reducir más energía de la que tiene disponible, la energía final será
0
.
-
virtual void run_mission() = 0;
- Método con el que la nave ejecutará una misión.
- Activará su atributo
in_mission
, reducirá la energía en una determinada cantidad, según el tipo de nave, y activará la señalmission_sig
.
-
mission_sig_t &get_mission_signal();
- Devuelve la señal
mission_sig
..
- Devuelve la señal
2. ImperialShip
y RebelShip
(hereda de Ship
)
- Estas clases heredan de Ship y tendrán un constructor y un destructor por defecto que no hará nada. El constructor se encargará de inicializar los atributos.
3. Las clases TIEFighter
y StarDestroyer
(heredan de ImperialShip
)
- Implementan
run_mission(...)
tal y como se menciona enShip
y teniendo en cuenta la pérdida de energía.TIEFighter
pierde 10 unidades de energía por misión;StarDestroyer
pierde 50 unidades.
- Cada una tendrá un destructor que no hará nada y un constructor que recibe dos parámetros: char symbol, uint energy. La función utilizará estos datos para inicializar los atributos del objeto.
4. XWing
(hereda de RebelShip
)
- Implementa
run_mission(...)
tal y como se menciona enShip
y teniendo en cuenta la pérdida de energía.- Pierde 20 unidades de energía por misión.
- Tendrá un destructor que no hará nada y un constructor que recibe dos parámetros: char symbol, uint energy. La función utilizará estos datos para inicializar los atributos del objeto.
5. Container<T>
(genérica)
-
Contenedor de elementos con nombre. Se trata de un contenedor genérico que puede contener cualquier tipo de dato.
-
Atributos:
- std::string cname; nombre del contenedor.
- std::vector
items; vector de elementos de tipo T. Recuerda que T no es un tipo de dato creado por ti, si no que hace uso de una plantilla.
-
Funciones:
-
friend std::ostream &operator<<(std::ostream &os, const Container<U> &c);
-
Operador de salida que implementará cómo se va a representar el contenedor en un ostream (como la salida de consola).
-
Primero, si el nombre del contenedor no tiene una cadena vacía, se muestra el nombre y un salto de línea.
-
Segundo, se mostrarán todos los elementos del vector, uno a uno, y cada uno en una línea. Como el contenedor puede tener objetos o punteros, deberás comprobar cuál de estos dos es el caso, e invocar al operador de salida del elemento (no del puntero).
-
Para ello, puedes hacer uso de esto: std::is_pointer<U>::value. Esta instrucción te devolverá true si el tipo de dato
U
se trata de un puntero y false en caso contrario. Si se trata de un puntero, asegúrate de no estar mostrando en la consola la dirección de memoria que contiene. Puedes encontrar más información sobre esta instrucción en cplusplus o en cppreference. -
Container(const std::string& name=“Container”);
- Constructor que inicializará el nombre del contenedor con el parámetro recibido. El parámetro
name
debe de tener como valor por defecto “Container”, tal y como se ve en la declaración de la función. Recuerda que el valor por defecto es aquel que se le otorga a un parámetro cuando este no es proporcionado en la llamada a la función. Se trata de un parámetro opcional.
- Constructor que inicializará el nombre del contenedor con el parámetro recibido. El parámetro
-
virtual ~Container() = default;
- Destructor por defecto. No hará nada.
Métodos accesores (setter y getter) del atributo
name
.-
void set_name(const std::string& name);
-
const std::string& get_name() const;
-
void add(const T& item);
- Añade un nuevo elemento al final del vector de items.
-
uint16_t find(const T& item);
- Busca un item en el vector de items del contenedor y devuelve la posición dónde lo ha encontrado. Devolverá el mayor valor posible cuando no encuentre el elemento en el vector.
-
void remove(const T& item);
- Borra el elemento recibido por parámetro del vector de items del contenedor. Si no existe, el método no hace nada.
- Si existen varios items repetidos en el contenedor, borraremos todos los que coincidan con el item recibido por parámetro.
-
T& operator[](size_t i);
- Devuelve el elemento de la posición
i
del vector de items del contenedor. No necesitas comprobar si existe dicha posición. En caso de que no exista, el programa se comportará igual que cuando utilizas un vector.
- Devuelve el elemento de la posición
-
size_t size() const;
- Devuelve la cantidad de elementos dentro del vector de items.
-
6. Hangar
(hereda de Container<ShipPtr>
)
- Funciones:
-
Hangar(const std::string& name);
- Constructor que inicializará el nombre del Hangar.
-
virtual ~Hangar() = default;
- Destructor por defecto (no hace nada).
-
void remove(size_t index);
- Elimina una nave del hangar. Debes comprobar si el índice recibido existe. Si no existe tal posición en el vector de naves, no se hace nada.
-
friend std::ostream &operator<<(std::ostream &os, const Ship& s);
- Operador de salida que añadirá al parámetro ostream una representación del hangar para mostrarlo en consola. Esta función tendrá un comportamiento similar al operador de salida de
Container
, excepto que, la lista de naves aparecerá en una misma línea, en vez de lo que ocurre en Container, que separa los items por salto de línea. Por ejemplo, un hangar con nombre “Hangar I” con una nave TieFighter y otra XWing, tendría la representación:
Hangar I[T][X]- Las naves se listarán en la misma línea, utilizando el operador de salida propio de las naves. Fíjate que esta función debe añadir dos espacios antes de comenzar con la lista a modo de indentación (no tabulaciones).
- Si la lista de naves está vacía, se imprimirá una línea vacía después del nombre:
Hangar I- Si el hangar no tiene nombre (cadena vacía), directamente aparecerá la lista de naves, con los dos espacios a la izquierda:
[T][X]- Si el hangar no tiene nombre y además no tiene naves, se imprimirá el mensaje “Hangar vacío” añadiendo un salto de línea:
Hangar vacío- Dato curioso: Fíjate que Hangar hereda de
Container<ShipPtr>
. Esto quiere decir que, si no implementasemos el operador de salida de Hangar, podríamos utilizar directamente el operador de salida de Container para representar unHangar
. Sin embargo, si quieres una implementación específica paraHangar
diferente de la implementación deContainer
, es posible sobrescribir el operador de salida, tal y como estamos haciendo. Puedes probar a quitar el operador de salida deHangar
si quieres. Verás que el programa compilará y funcionará, aunque los hangares se mostrarán en consola de una manera ligeramente diferente. Si lo pruebas, recuerda volver a poner la implementación de esta función para la entrega de la práctica.
- Operador de salida que añadirá al parámetro ostream una representación del hangar para mostrarlo en consola. Esta función tendrá un comportamiento similar al operador de salida de
-
7. DeathStar
(hereda de Container<HangarPtr>
)
-
Contiene los hangares de la Estrella de la Muerte. Es la clase principal.
-
Atributos:
- Container
mission_ships; vector de naves que se encuentran en misión, y por lo tanto no se encuentran en ningún hangar.
- Container
-
Funciones:
-
DeathStar(const std::string& name);
- Constructor que inicializa el nombre del contenedor.
-
virtual ~DeathStar();
- Destructor por defecto (no hace nada).
-
void send_all_on_mission();
- Envía todas las naves disponibles en los hangares a realizar una misión.
- Cuando una nave salga del hangar para realizar una misión, debes quitarla del hangar y añadirla al vector
mission_ships
y cambiar el estado de la nave para indicar que se encuentra en una misión.
-
void recall_ship_to_hangar(ShipPtr ship, HangarPtr hangar);
- Traerá de vuelta una nave a un hangar determinado.
- Debes comprobar que el hangar existe en la estrella de la muerte, y que la nave se encuentra realmente en el vector
mission_ships
. Si no ocurre esto, entonces no se hace nada. Si se da el caso, entonces la nave habrá que moverla desdemission_ships
hasta el hangar correspondiente (proporcionado por parámetro).
-
4.3 Diagrama UML
- A continuación encontrarás el diagrama de clases que representa las clases y las relaciones entre ellas dentro de este proyecto. Ten en cuenta, que normalmente la elaboración de este diagrama se realiza antes de empezar a programar nada, en la fase de diseño, donde se especifica lo que queremos hacer y la estructura del programa que dará solución al problema a resolver. En este caso, el diagrama ya está hecho, y lo puedes emplear como esquema para entender la estructura del programa y la relación entre las clases. El diagrama también lo tienes en el código de partida.
Figura 1: Diagrama de clases UML que representa la estructura del proyecto que tienes que desarrollar.
5 Salida esperada con el mainp4.cc
proporcionado
- Para probarlo, primero compila con
make
, y después ejecuta./bin/p4
. Al utilizarmake
, los archivos generados tras la compilación, incluyendo el ejecutable, se guardarán en la carpetabin
. No necesitas crear dicha carpeta, se crea automáticamente. Sin moverte del directorio en la consola (estando situado en el mismo sitio donde tienes el ficheroMakefile
) puedes ejecutar./bin/p4
para probar el ejecutable generado.p4
es el nombre del ejecutable. El resultado que deberías tener tras ejecutar el código compilado con el ficheromainp4.cc
proporcionado en el código de partida es el siguiente:
======== ESTADO INICIAL ========Estrella de la MuerteHangar I [T][X]Hangar II [D]Hangar III [W]
======== ENVIANDO NAVES A MISION ========Signal: Ship T now has 90 energy.Signal: Ship X now has 90 energy.Signal: Ship D now has 450 energy.Signal: Ship W now has 180 energy.
======== HANGARES VACIOS ========Estrella de la MuerteHangar I
Hangar II
Hangar III
======== ORDEN DE REGRESO A LOS HANGARES ========
======== ESTADO FINAL ========Estrella de la MuerteHangar I [T][X]Hangar II [D]Hangar III
6 Archivos de partida.
En el código de partida dispones de los archivos de cabecera necesarios para la práctica además de un ejemplo de ejecución del programa. A continuación se listan los archivos de partida:
-
mainp4.cc
: contiene algunas pruebas básicas del programa. Puedes modificarlo para incluir las pruebas que creas conveniente. -
container.h
: cabecera que represente la clase Container. Esta clase es un contenedor de objetos, que pueden ser de cualquier tipo. Debería funcionar incluso con tipos básicos como int. -
deathstar.h
: cabecera de la clase Deathstar, representando la estrella de la muerte. Básicamente, es un contenedor de hangares. -
hangar.h
: cabecera de la clase Hangar. Se trata de un contenedor de naves. -
ship.h
: cabecera con la clase Ship. Los objetos de esta clase representan naves de cualquier tipo. -
imperialship.h
: cabecera de la clase ImperialShip. Representa una nave imperial. -
rebelship.h
: cabecera con la clase RebelShip. Representa una nave de la alianza rebelde. -
stardestroyer.h
: cabecera con la clase StarDestroyer, representando un destructor estelar. -
tiefighter.h
: cabecera con la clase TieFighter. Es una nave del imperio. -
xwing.h
: cabecera de la clase XWing, que es un tipo de nave de la alianza rebelde. -
Makefile
: se recomienda para compilar de forma sencilla. Recuerda la forma de utilizar la herramientamake
en la terminal:make
para compilar y enlazar. Todos los archivos generados para la compilación (incluyendo el ejecutable) los guarda en la carpetabin
.make tgz
para generar el archivo comprimido que deberás entregar.make clean
para borrar los archivos compilados.
-
Todos los archivos se encontrarán dentro de una carpeta llamada
irp2-p4
, y a su vez, los archivos de cabecera y mainp4.cc estarán en una subcarpeta llamadasrc
, directorio en el que tendrás que implementar tu código. -
No modifiques ningún archivo de cabecera. Si lo haces, te arriesgas a que tu implementación no cumpla los requisitos del servidor y no compile.
7 Archivos que debes implementar
Los archivos que debes implementar son los siguientes:
- container.tcc: implementación de la clase
Container
. Fíjate que la extensión es.tcc
, no es.cc
. - deathstar.cc: implementación de la clase
DeathStar
. - hangar.cc: implementación de la clase
Hangar
. - ship.cc: implementación de la clase
Ship
. - imperialship.cc: implementación de la clase
ImperialShip
. - rebelship.cc: implementación de la clase
RebelShip
. - xwing.cc: implementación de la clase
XWing
. - stardestroyer.cc: implementación de la clase
StarDestroyer
. - tiefighter.cc: implementación de la clase
TieFighter
.
Nota: Fíjate que los nombres de los archivos son en minúsculas, tanto los archivos de cabecera (.h
) como los de implementación (.cc
y .tcc
). Como recomendación, antes de empezar a desarrollar la práctica, puedes crear todos estos ficheros para que no se te olvide ninguno después. Recuerda poner el nombre y el NIF en todos los ficheros de implementación.
8 Entrega y requisitos técnicos.
-
Requisitos que tiene que cumplir este trabajo práctico para considerarse válido y ser evaluado (si no se cumple alguno de los requisitos la calificación será cero):
-
El archivo entregado se llama
irp2-p4.tgz
(todo en minúsculas). Dicho archivo debe contener una carpeta llamadairp2-p4
que contendrá todos los archivos a entregar. Dentro de esa carpeta, necesitas incluir todos los archivos de implementación listados en el apartado anterior (apartado 7). Si entregas los demás archivos, no pasa nada. El corrector solo utilizará los archivos solicitados. -
El archivo comprimido lo puedes crear de dos formas.
- Utilizando la herramienta
make
con el archivoMakefile
que tienes en los archivos de partida con la instrucción:make tgz
. - Situandote manualmente en la carpeta padre de tu carpeta
irp2-p4
y tecleando:tar cfz irp2-p4.tgz irp2-p4
- Utilizando la herramienta
-
Una vez comprimido, ábrelo y asegúrate de que tiene la estructura que se pide. Si el servidor de entrega no detecta alguno de los archivos, es porque no estás entregando el archivo comprimido con la estructura que se pide. En este caso, revisa el nombre de la carpeta que contiene y el nombre de los ficheros que entregas.
-
IMPORTANTE: Todo el código del proyecto, incluyendo los archivos de cabecera (
.h
) y los archivos de implementación (.cc
y.tcc
) junto almainp4.cc
deben estar en la carpetasrc
dentro de la carpetairp2-p4
. La estructura necesaria del archivo comprimido que tienes que entregar será:
irp2-p4.tgz└── irp2-p4 └── src ├── container.tcc ├── deathstar.cc ├── hangar.cc ├── ship.cc ├── imperialship.cc ├── rebelship.cc ├── xwing.cc ├── stardestroyer.cc └── tiefighter.cc
-
Recuerda que los ficheros ‘.h’ no se deben modificar, de lo contrario tus ficheros ‘.cc’ podrían no compilar en el corrector. No es necesario entregar los ficheros ‘.h’ ni el ‘mainp4.cc’. Si se hace no pasa nada. Tampoco pasa nada si entregas el
Makefile
. -
Las clases, métodos y funciones implementados deben tener los nombres exactos que se indican en el enunciado (respetando en todo caso el uso de mayúsculas y minúsculas). También es imprescindible respetar estrictamente los textos y los formatos de salida que se indican en este enunciado.
-
Al principio de todos los ficheros fuente (`.h’ y `.cc’) entregados y escritos por ti se debe incluir un comentario con el nombre y el NIF (o equivalente) de la persona que entrega la práctica, como en el siguiente ejemplo. Ponlo exactamente igual, cambiando los datos por los tuyos.
// NIF: 12345678-Z// NOMBRE: GARCIA PEREZ, LAURA -
Un error de compilación/enlace implicará un cero en esa parte de la práctica.
-
Se utilizará valgrind para comprobar que no haya fugas de memoria en tu programa. Puedes instalarlo y probarlo antes de realizar la entrega para asegurarte de corregir posibles errores relacionados con la gestión de la memoria.
-
Lugar y fecha de entrega :
Recuerda que las entregas se realizan siempre en (y sólo en)
https://pracdlsi.dlsi.ua.es en las fechas y condiciones allí publicadas. Puedes entregar la práctica tantas veces como quieras, sólo se corregirá la última entrega (las anteriores no se borran). El usuario y contraseña para entregar prácticas es el mismo que se utiliza en UACloud.
8 Detección de plagios/copias.
-
En el Grado en Ingeniería Robótica, la Programación es una materia muy importante. La manera más efectiva de aprender a programar es programando y, por tanto, haciendo las prácticas correspondientes además de otros programas (más sencillos o más complicados, eso da igual).
-
Tratar de aprobar las asignaturas de programación sin programar (copiando) es complicado y obviamente, te planteará problemas para seguir otras asignaturas así como en tu futura vida laboral.
-
En una asignatura como Programación-II es difícil que alguien que no ha hecho las prácticas supere los exámenes de teoría o el de recuperación de prácticas.
-
IMPORTANTE:
-
Cada práctica debe ser un trabajo original de la persona que la entrega.
-
En caso de detectarse indicios de copia de una o más prácticas se tomarán las medidas disciplinarias correspondientes, informando a las direcciones de Departamento y de la EPS por si hubiera lugar a otras medidas disciplinarias adicionales.
-