I have this code that has turned into a little maze. I have a dynamic array made of a BASE class called MazePlace and I have two DERIVED classes that are wall and OpenSpace
Basically what I want is to read a text file with a maze that can have variable dimensions and read the file character by character and recreate the maze inside an array of pointers
LugarLaberinto*** lugares;
but the derived class OpenSpace has methods that allow me to change the Open space with for example the person inside the maze.
So my question is how to access a method of the OpenSpace class through a pointer of a MazePlace class ?
and here I leave the code with the comments. (I tried to make the code as small as possible to show the doubt, but I couldn't reduce it more than this. Thanks)
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
//####################################
//clase BASE lugar del laberinto
class LugarLaberinto
{
public:
virtual char mostrarCaracter()=0;
protected:
char caracter;
};
//####################################
//clase muro DERIVADA de lugar del laberinto
class muro : public LugarLaberinto
{
public:
muro();
~muro();
char mostrarCaracter();
};
muro::muro()
{
caracter = '#';
}
char muro::mostrarCaracter()
{
return caracter;
}
//####################################
//clase espacioAbierto DERIVADA de lugar del laberinto
class EspacioAbierto : public LugarLaberinto
{
public:
EspacioAbierto();
~EspacioAbierto();
char mostrarCaracter();
// funcion de la clase derivada
void hayAlguien(bool);
};
EspacioAbierto::EspacioAbierto()
{
caracter=' ';
}
char EspacioAbierto::mostrarCaracter()
{
return caracter;
}
void EspacioAbierto::hayAlguien(bool)
{
caracter='@';
}
//####################################
//clase laberinto
class laberinto
{
public:
//constructor que toma de un stream los valores del laberinto
laberinto (std::ifstream&, int, int);
~laberinto();
LugarLaberinto*** lugares;
LugarLaberinto* obtenerLugar(int, int);
};
laberinto::laberinto(ifstream& fin, int alto, int ancho)
{
char bloque;
string auxiliar;
lugares = new LugarLaberinto**[alto];
//fin.ignore();
for (int i=0; i< alto ; i++)
{
lugares[i] = new LugarLaberinto*[ancho];
// leo una linea completa del archivo laberinto
// y la guardo en auxiliar
getline(fin,auxiliar);
//cout << auxiliar;
for (int j=0; j< ancho; j++)
{
//leo cada uno de los caracteres y los almaceno en la variable bloque
bloque = auxiliar [j];
// Basado en si es un # o si es un espacio basio creo el puntero
//hacia lugar de laberinto
if (bloque=='#')
this->lugares[i][j] = new muro();
else
{
this->lugares[i][j] = new EspacioAbierto();
//########################
//aqui esta mi duda
// me gustaría acceder al metodo
// void hayAlguien(bool)
// a traves del puntero lugares
// sin embargo no se si sea posible
// o no se como hacerlo
/*
// aqui dice que la clase lugar laberinto no tiene
//un miembro llamado hayAlguien y bueno eso lo sé
// pero no quiero crear una funcion virtual para cada miembro
// de esta clase derivada
if (bloque=='@')
this->lugares[i][j]->hayAlguien(true);
*/
// encontré esta manera de hacerlo pero no se si es la única
// que es creando un nuevo objeto modificandolo y luego
// asignándolo, sin embargo no me siento totalmente cómodo
// pensando que tengo que crear objetos a cada instante que
// quiera modificar el objeto.
if (bloque=='@')
{
EspacioAbierto *aux= new EspacioAbierto();
aux->hayAlguien(true);
lugares[i][j]=aux;
}
}
//########################
cout << this->lugares[i][j]->mostrarCaracter();
}
cout << endl;
}
}
int main(int argc, char** argv) {
ifstream fin;
fin.open("laberinto.txt");
int alto = 10;
int ancho =10;
laberinto *nivel = new laberinto(fin, alto, ancho);
return 0;
}
to finish I attach the file labyrinth.txt
##########
#@# #
# ##### #
# #
###### #
# #
# #######
# # #
# e#
##########
You have 4 ways:
I rule here
If you are absolutely sure that your pointers refer to the correct classes, you can use a type cast . Using these 2 methods, it is your responsibility to check that a pointer to the base class actually points to an instance of the child class.
These 2 forms force the compiler to accept without question what you tell it, no matter how illogical the conversion is.
Use the expression
(clase_derivada *)puntero_a_clase_hija
:This syntax is inherited from C. You shouldn't use it, as it's hard to look between lines of code if you need to make changes for some reason.
It is similar to the previous one, but we use the reserved word
reinterpret_cast
.This syntax is much easier to localize; just do a search for
reinterpret_cast
in the text to find it. It facilitates later changes, and uses a more C++-style syntax .I'm good, but just in case
static_cast< >( )
.This can be considered a gentler method than the previous ones. It doesn't force the compiler to support anything; will show us an error if the types involved are clearly incompatible.
C++ Help me!
Since you're in C++, you can take advantage of it and let the language runtime check that an instance belongs to an inheriting class of another.
To take advantage of this, classes must have virtual methods , which you already have in
LugarLaberinto
.dynamic_cast< >( )
.Similar to the above:
As you can see, we make a somewhat different use . The operator
dynamic_cast
returns a pointer if the conversion is possible, orNULL
if the conversion is not possible (for example, because the original pointer points to a non-child class).Also, if we use it with references , instead of returning
NULL
(which is not possible with references), it will throw the exceptionstd::bad_cast
if the conversion is not possible.Of the 4 possible ways,
dynamic_cast
it is the only one with cost in execution time; all the others are done at compile time ; the latter suffers a minimal penalty, since a check is made on whether or not the pointers to the classes are actually compatible (using `the VTABLE; hence the need for virtual methods).@Trauma's answer seems correct to me, but in general that if/downcast mechanism can be overridden using a virtual function. For example, your base class could be:
This mechanism is often called a "Service request", which the user can invoke to ask an object to execute an action, if it is available. You can find the details in
Come on, one more alternative... visitor pattern:
One of the main advantages is that it is not necessary to carry out conversions of any kind since, unless you do strange things, you will always work with valid types.
The logic of this pattern at first clashes a bit but the truth is that it is quite simple:
visit()
. This function is virtual and this is where the magic begins to work.lista[i]->visit()
you call the methodvisit()
corresponding to the current type (A
,B
orC
) will be called. Each of the implementations interacts with the objectVisitor
in a different way (VisitA
,VisitB
orVisitC
).VisitX
toVisitor
work with a particular type of object, so it has access to its entire public interface.This pattern can be quite powerful if it is implemented with interfaces or, in the case of C++, with abstract classes. This way you can have a
Visitor
basically dumb base class and then a whole collection of specific visitors to do various tasks:The programming possibilities are endless and the good thing is that the visitation logic only has to be programmed once.