Código (cpp) [Seleccionar]
Tarjeta::Tarjeta (const Numero& numero, const Usuario &user, const Fecha& caducidad) : numero_(numero), user_(&user), caducidad_(caducidad) {
Fecha f_actual;
titular_ = user.nombre() + " " + user.apellidos();
if (caducidad_ < f_actual)
throw(Tarjeta::Caducada(caducidad_));
//(&user).es_titular_de(*this);
}
es decir, user tiene el modificador const.
La llamada es_titular_de tiene la firma:
Código (cpp) [Seleccionar]
void es_titular_de(Tarjeta&)
Es decir, no tiene el modificador const.
Cuando tu declaras una instancia como const quiere decir que, desde ese momento, no se puede modificar su estado. Esto implica que no se permiten las llamadas a métodos no const para dicha instancia.
Hasta ahí tu error. Luego hay varias cosas que no entiendo:
1. es_titular_de no tiene sentido que retorne void, es como preguntar a alguien la hora por la calle, que el otro la vea y no te responda... si lo que hace la implementación es imprimir por pantalla el resultado, mi consejo es que lo saques fuera de la clase.
Las razones son sencillas:
* La clase que contiene la función se llama Usuario, luego se presupone que su misión es gestionar la información de un usuario y no encargarse además de imprimir sus datos por pantalla, guardar los datos en un fichero y llamar a su novia en los aniversarios... responsabilidades definidas.
* Concentrar múltiples responsabilidades conlleva concentrar el código, lo que dificulta las tareas de depuración.
* El código se oscurece porque las estructuras de datos tienen a diluirse.
Tiene más sentido que la función luzca tal que
Código (cpp) [Seleccionar]
bool es_titular_de( const Tarjeta& ) const;
2. Tiene sentido crear instancias de "Caducada" con el constructor por defecto??
Entiendo que no, pero sin embargo, al no definir el constructor por defecto en la sección "private" se crea un constructor por defecto. Con poner solo la declaración en el archivo de cabecera vale. En C++11 también puedes impedir el uso del constructor por defecto de la siguiente forma:
Código (cpp) [Seleccionar]
class Caducada
{
public:
Caducada( ) = delete;
};
Lo mismo es aplicable a las clases Tarjeta y Usuario.
3. Esta línea
Código (cpp) [Seleccionar]
const Usuario * const user_; //Puntero constante al usuario dueño
te puede dar más problemas que alegrías. Sobretodo porque...
Código (cpp) [Seleccionar]
const Usuario* titular() const { return user_; }
Es decir, no hay ningún problema en que se modifiquen los datos del usuario.
Personalmente, dado que no hace falta que la clase cree el puntero, casi es más limpio pasar y almacenar una referencia, así evitas la "tentación" de poner un delete en un momento dado. Además, pasar clases por referencia te asegura de que la clase existe, ya que no se puede pasar un puntero a null por referencia.
Código (cpp) [Seleccionar]
class Tarjeta
{
public:
Tarjeta (const Numero& numero, const Usuario& usuario, const Fecha& fecha )
: user_( usuario )
{ }
Usuario& titular() const
{ return user_; }
private:
Usuario& user_;
};
4.
Código (cpp) [Seleccionar]
throw(Tarjeta::Caducada(caducidad_));
No es buena idea lanzar una excepción que no herede de std::exception. El motivo es que, si no heredan de std::exception, la captura por defecto es catch( ... ) y en este caso pierdes toda la información acerca de la excepción. Además, siempre es recomendable que la excepción incluya un texto descriptivo... es útil en sistemas más complejos para seguir la traza del fallo.
5.
Código (cpp) [Seleccionar]
titular_ = user.nombre() + " " + user.apellidos();
Este campo autocalculado no tiene demasiado sentido... nombre() y apellidos() no son llamadas a operaciones costosas, es mejor autocalcular el valor cuando sea necesario, como los puntos anteriores, no es un fallo, solo una cuestión de diseño.
6. Las clases se crean sólo si hay razón para ello
Código (cpp) [Seleccionar]
class Id_duplicado {
public:
Id_duplicado (const Cadena& id) : id_duplicado(id) {}
const Cadena idd() { return id_duplicado; }
private:
const Cadena id_duplicado;
};
Vista así, esta clase no aporta nada, parece totalmente prescindible. Bien es cierto que lo ideal es tener el código bien repartido en muchas clases, cada una con una única responsabilidad, y con el menor código posible... pero claro, estas clases tienen que tener una razón de ser y esta, vista así, no lo tiene. Llenar el código de clases inútiles te va a dificultar el mantenimiento de tu código y te va a dar problemas en el futuro... estás avisado.