templates
-
domingo, 21 de noviembre de 2010 21:22
Tengo un Template que debo instanciar despues de obtener en run-time los datos aportados por el usuario. El problema no está tanto en como instancia la plantilla adecuada, problema que en su forma mas simple podría realizar on un simple if(...) template<A>... if then(...) template<B>... etc...
El problema es que el resto del programa debe utilizar los metodos de la template, pero si quiero acceder a esos metodos necesariamente tengo que decir de que tipo es el template, esto es, cada vez que quiero acceder a dichos datos necesito especificar los parámetros de plantilla.
Si podeis ayudarme y orientarme un poco de como podría hacerlo estaría muy agradecido... no quiero que programeis por mi, ... solo sugerirme el como debo hacerlo, ... implementando un Design Pattern particular, ... en fin, ... cualquier sugerencia será bienvenida
Todas las respuestas
-
lunes, 22 de noviembre de 2010 0:07
Para eso se han inventado las funciones virtuales. Create una clase base con las funciones que necesitan todos los templates y las defines virtuales. Deriva todos los templates de esa clase base y llama siempre a las funciones de la base. C++ se encargará de llamar a las funciones de los templates:
class BaseTemplates { public: virtual void Funcion1(void); virtual int Funcion2(int a, int b); //... }; template<class C> class Template1 : public BaseTemplates { public: Template1(const C &V); void Funcion1(void); int Funcion2(int a, int b) //... }; template<class C1, class C2> class Template2 : public BaseTemplates { public: Template2(const C1 &V1, const C2 &V2); void Funcion1(void); int Funcion2(int a, int b) //... }; // en el código BaseTemplate *pTemplate=NULL; if (...) pTemplate = new Template1<int>(34); else if (...) pTemplate = new Template2<char,double>('a',45.7); else pTemplate = new Template2<int,char>(7,'Z'); // y para llamar a las funciones pTemplate->Funcion1(); int b = pTemplate->Funcion2(4,8); //...
Las funciones virtuales de la clase base las puedes hacer virtuales puras o no a tu conveniencia.
- Marcado como respuesta RFOGMVP, Moderator lunes, 22 de noviembre de 2010 21:14
-
sábado, 11 de diciembre de 2010 20:16
Hola Bartomeu... antes de nada darte las gracias por tu respuesta y pedirte disculpas por tardar tanto en responder; la verdad es que dejé activada la casilla de "..notificarme cuando alguien responda.." y menos mal que se me ocurrió mirar porque no recibí notificación alguna. En fin, ... disculpas de nuevo.
En cuanto al tema que nos ocupa, la solución que me propones no vale para lo que pretendo. Verás quiero crear un objeto versátil, que me sirva para almacenar una variable con independencia de su tipo (int, float, string, ...) y quiero almacenar dichos objetos en un contenedor, de tal forma que las peticiones del codigo cliente se harían a través del contenedor. El problema radica en que como tales objetos guardan cada uno un tipo de variable desconocida a priori (obtenida en runtime), el diseño se complica a la hora de obtener la variable que guarda cada objeto. Un esbozo sencillo de la idea sería como sigue:
class Basic {
virtual void obtener_valor() = 0; //virtual pura o vacía de contenido
template <class T> T devolver_valor(); //solo puedo declararla pero no definirla en el ambito de Basic
};
template < class Tipo >
class Objeto : public Basic { //clases heredadas de Basic, cada una tiene una variable de un tipo dado
Tipo variable; //la variable de la discordia, ... será de un tipo dado
public:
void obtener_valor() { //por comodidad defino aquí; especialización para string....
cin >> variable;
}
Tipo devolver_valor() { return variable; }
//por comodidad defino aqui, pero es evidente que debiera de definirla fuera de la clase y con instanciación explicita
};
class ContenedorObjetos {
vector<Basic * > vec;
void insertarObjeto( int tipo ) {
if( tipo == CADENA ) vec.push_back( new Objeto<string> )
if( tipo == ENTERO ) vec.push_back( new Objeto<int> ) ... etc....
}
template <class T>
T devolver( string nombreObjeto ) { // me he saltado qeu cada objeto dispondrá de un nombre que lo identifica, el caso es que se buscaria en el vector aquel objeto cuyo nombre coincide y se llamaría a la función del objeto en cuestión ... }
el problema está en que tendría que saber el programa o rutina cliente, el tipo de dato que se asocia con dicho objeto, es decir habría que hacer la llamada:
int main() {
ContenedorObjetos contenedor;
contenedor.devolver<string>( "NOMBRE_DEL_OBJETO");
logicamente no es admisible este comportamiento, pues en main no se sabe a priori hasta runtime que el objeto en cuestion es de tipo string, int, ... etc...
Supongo que habrá algún diseño dentro de la OPP que ya haya toreado con este tema, ... He estado rastreando por los distintos libros y tal, y he visto diseños muy interesantes, ... pero ninguno encaja bien en lo que pretendo hacer y además se complican mucho las cosas. Entiendo que debe de haber algo mas simple, que evidentemente no he hecho para conseguir la funcionalidad que pretendo, ... no se, a través de clases policy, traits, ... implementando algún patron de diseño, ... en fin, ... algo ya habrá.
Si te apetece darle una vuelta al problema, yo estoy metido en ello expectante y abierto a todo tipo de sugerencias.
En cualquier caso Bartomeu, darte las gracias de nuevo por haber respondido. Un saludo
Por cierto me llamo Roberto.
-
lunes, 13 de diciembre de 2010 14:48
Hola Roberto, antes de nada, a mi tampoco me funciona el 'Enviarme una alerta'. Ahora pasemos al asunto.
Creo que tienes un fallo básico de concepto. Si en un programa con clases/funciones virtuales tienes que poner una secuencia de if encadenados (o un switch) preguntando el tipo de la clase/funcion para hacer algo, es que están mal diseñadas las clases/funciones virtuales. Cuando juntas clases/funciones virtuales con templates es para poder añadir una clase/funcion que actuará sobre un tipo nuevo sin tener que preocuparte de bucear por todo el código ya tecleado retocando las cadenas de if o los switch para añadir el nuevo tipo. Es así de simple/complicado. Hay que saber cojerle el truco, y lleva su tiempo.
Te recomiendo que te centres en ¿Qué tiene que hacer 'contenedor.devolver<string>( "NOMBRE_DEL_OBJETO")' En tu ejemplo no dices que quieres hacer. Es imposible que uses con un igual, si haces:
int a = contenedor.devolver<string>("nombre");
te dará un error, no puedes poner un string en un int. Asimismo, no puedes controlar si el usuario pondrá "nombre" o "nombra" ( y en "nombra" no sabes si habrá un string o un double) no puedes saber en tiempo de ejecución lo que devolvera, y, por lo tanto, no lo puedes asignar a un int,
Si lo que quieres hacer es escribirlo en un stream
std::cout << contenedor.devolver<string>("nombre")
si que lo podrás hacer, ya que << es un template.
pero te sobra el <string>.
mira el siguiente contenedor:
class ContenedorObjetos { public: template <class T> void Insertar(string Nombre, T Valor) {Map[Nombre]=new Objeto<T>(Valor);} Basic *Devolver_pBasic(string Nombre) {return Map[Nombre];} string Devolver_string(string Nombre) {return Map[Nombre].str();} private: std::map<string,Basic *> Map; };
Hace todo lo que pretendes, incluso al usar std::map en vez de std::vector, te ahorras la busqueda de los string en tu vector. Estudiate el map y cuidado con sus particularidades: al hacer Map[Nombre] siempre te devolverá algo, si Nombre ya existia te devolverá lo que pusiste, pero si no existia lo insertará y te devolvera un null. Tiene una manera de preguntar si existe sin insertarlo, pero te lo dejo para que lo investiges.
Primero centrate en esta clase para que haga todo lo que quieres que haga y después pasa ha diseñar las funciones virtuales de Basic, (en mi ejemplo una tendria que ser 'string str(void)=0") que en el template de Objeto se podria definir/implementar:
template <class T> class Objeto : public Basic { //... string str(void) { std::ostringstream Buf; Buf << variable; return Buf.str(); } //... private T variable; };
-
martes, 14 de diciembre de 2010 8:12
Hola de nuevo Bartomeu. El tema de los if, switch, etc... era por abreviar y no extenderme. Espero no darte demasiado el coñazo con mi problemilla, pero en aras de la claridad debería explicarte que es lo que quiero hacer y luego te comentaré los problemas con los que me encuentro.
Basicamente quiero construir un objeto "CAMPO", si, como el típico campo de una base de datos (tipo cadena, tipo numero, ... bla, bla, ...) y pretendo que el campo gestione todo lo relacionado con el valor que guarda. De esta forma el campo es responsable de (a una peticion del cliente) obtener el dato a guardar (de la consola de momento) hacer cosas distintas con el dato, ..., y por supuesto devolver el dato (a petición del cliente). Vamos a dejarlo así de sencillo, sin capturar excepciones, ni control de errores ...
El cliente que llama al objeto CAMPO, es un objeto que se ha encargado previamente de obtener de un fichero en disco las características de un campo cualquiera; supongamos que se lee del disco esta informacion: Nombre_Campo "Titulo", Tamaño_Campo "60", Tipo_Campo "ALFANUMERICO", etc ...
Lo que pretendo es crear ese objeto CAMPO que, a petición del cliente, se encargue de obtener el dato que ha de guardar, una cadena, un coma flotante, una cadena unicode, un entero etc... y posteriormente a petición del cliente, devuelva el dato que tiene guardado; así un Campo<string> devolverá una cadena de texto, un Campo<float> devolverá un real, etc...
Asimismo quiero diseñar un contenedor que se encargue de crear los campos y de pasar las peticiones del cliente a cada CAMPO en particular, en virtud del nombre del campo.
He probado distintas técnicas: paso de politicas al contenedor, utilizar una clase base abstracta CAMPO_BASICO y de ella heredar como plantilla los distintos CAMPOS
template<class T> class CAMPO : public CAMPO_BASICO { .....}
El problema es que la clase base que hace de interface tiene que declarar (en el caso mas sencillo) dos funciones virtuales void obtener_dato(), que será llamada desde el contenedor y template<class T> T devolver_dato(). Dichas funciones tendrían que ser llamadas desde el contenedor que guarda punteros a todos los campos via un vector (o un map...) vector<CAMPO_BASICO * > ...
El problema está en que una funcion plantilla no puede ser virtual. Además no podría declarar el tipo devuelto por la función ya si en la clase base utilizo una plantilla, no puedo definirla en su ámbito, pues si declaro
class CAMPO_BASICO {
...
template <class T>
T devolver_dato() { return dato; }.
dentro del ambito de dicha clase la variable de tipo "T" dato no estaría en CAMPO_BASICO, sino en sus clases derivadas; template <class T> class CAMPO { T dato;....}.
Repasando un poco la cosa y simplificandola al máximo. Supongamos que en main() instancio un objeto CONTENEDOR, leo un fichero en disco y necesito para ese fichero 7 campos, dos de tipo cadena alfanumerica, uno de tipo bool, 3 de tipo float y uno de tipo entero. Mi contenedor ofrece un método crear_campo( nombre_campo, tipo_campo ) donde nombre campo es una cadena estraída del disco y tipo campo un valor numerio que indica el tipo de campo a crear (ej: CADENA = 0, REAL = 1, etc...). Ofrece otro metodo leer_dato( nombre_campo ) que busca en el vector (o map) el nombre y lo asocia al puntero correcto de la jerarquía, a partir de aquí dicho puntero llama a la funcion obtener_dato() que se encargaría de leer de la consola lo que escribe el usuario y lo guardaría en su variable. Posteriormente main() solicita a traves del contenedor que se devuelva la variable que guarda el campo de nombre "nombre_campo" para hacer algo con ella, así desde main se solicitaría al contenedor algo parecido a contenedor.traer_dato( nombre_campo ) que buscaría como antes dentro del contenedor el campo que responde a dicho nombre y ejecutaría a través de su puntero la función devolver_dato() para ese campo en particular.
Desde main() no se puede saber de que tipo es el dato que se quiere recuperar, si se sabe de que campo se quiere leer, pero no de que tipo es el dato. La idea básica es que alguna rutina hará algo con ese dato, pongamos por caso buscarlo en un indice ( buscar( obtener_dato( nombre_campo ) ) o por ejemplo guardarlo en un stringstream para luego descargar el contenido del stringstream al disco, ...
He probado multitud de técnicas, ... pero todas adolecen de algun tipo de problema. Por ejemplo he intentado pasar la función template<class T> T devolver_dato() como friend de CAMPO_BASICO, pero el compilador protesta y no me deja hacerlo (algo haré mal).
Supongo que habrá alguna forma ya estudiada de realizar esto; no se, quizás aplicando algún patron de diseño (visitor, command, ... ). En fin que este tema, si bien parece una chorrada, se me ha planteado como uno de los problemas mas complicados de resolver.
Espero haber aclarado un poco el asunto; la verdad es un tanto dificil en este foro, de edición un tanto limitada, poder explicar con cierta brevedad la cosa.
No quiero abusar de tu paciencia, simplemente agradecerte tu generosa y desinteresada colaboración y bueno, ... si se te ocurre algo, ... ya sabes, ... por aquí seguiré peleando.
Un saludo Bartomeu
-
miércoles, 15 de diciembre de 2010 20:50
Se me ha ocurrido algo. Te adjunto un esquema, algo elavorado, que creo te puede servir. Primero un par de conceptos básicos que, personalmente, creo fundamentales.
Primero: Cuando trabajamos con funciones virtuales, los valores de retorno y los argumentos tienen que ser iguales. Sólo en el caso de retorno de punteros hay un caso especial que el retorno puede ser aparentemente diferente sin perder la virtualidad, pero no es necesario aplicarlo en tu caso. Por consiguiente, una función virtual de campo<T> nunca podrá devolver ni T, ni T* ni T& ya que el valor de retorno será diferente y, automaticamente dejará de ser virtual. Conclusión, tienes las funciones virtuales mal diseñadas.
Segundo: En tu caso lo tienes mal planteado. Estrictamente hablando, cuando tu le pides datos a una fuente nunca obtienes un int, ni un float, ni un string, realmente obtienes una ristra de carácteres que, siguiendo unas reglas, interpretas como un int, un float o un string. Igualmente cuando tu le muestras los datos al usuario, nunca le muestras un int, un float o un string, realmente le muestras una ristra de carácteres que el usuario interpreta como un int, un float o un string. Por consiguiente los argumentos/retornos de tus funciones virtuales deben ser strings, que cada clase derivada en su función virtual traducirá y tratará como lo que sea necesario.
Por consiguiente, tu clase base (que llamaré simplemente campo) deberá ser algo muy parecido a esto:
class campo { public: enum tipo {INT, DOUBLE, ALFANUM /*...*/}; const tipo mTipo; // ¡¡no se puede cambiar el tipo!! virtual ~campo(void) = 0; static campo *Crear(tipo Tipo); virtual bool ValorValido(string Valor) const = 0; virtual void AsignarValor(string Valor) = 0; virtual string MostrarValor(void) = 0; virtual std::ostream &OutStream(std::ostream &Out) = 0; virtual std::istream &InStream(std::istream &In) = 0; virtual campo *Clonar(void) const = 0; // Para sumar campos virtual campo *Incrementar(const campo *Sumando) = 0; virtual campo *Sumar(const campo *Sumando) const = 0; // Otras operaciones con campos //... protected: // No puede existir un 'campo' a secas. Debe crearlo una clase derivada campo(tipo Tipo) :mTipo(Tipo) {} }; inline std::ostream &operator<<(std::ostream &Out, const campo &Campo) {return Campo.OutStream(Out);} inline std::istream &operator>>(std::istream &In, const campo &Campo) {return Campo.InStream(In);}
Notarás que, salvo una función estática que servirá para crear campos (ya volveré mas tarde sobre esta función), todas las funciones son virtuales puras. Esto implica que, aunque un campo sabe que tipo de campo es (INT, ALFANUM,... en mTipo) y sabe lo que puede hacer (AsignarValor, Sumar,...) realmente no sabe como hacerlo. Te he puesto las funciones AsignarValor y MostrarValor, ya que pareces obsesionado con ellas, pero al final verás que para sacar/meter campos en un fichero basta con operator>> y operator<< que llaman a InStream y OutStream respectivamente.
Para indicarle a la clase base como tiene que hacer lo que debe hacer, definimos una clase derivada mediante un template parecido a este:
template <class T> class t_campo: public campo { public: ~t_campo(void) {/*por si hay que hacer algo especifico en alguna derivada*/} bool ValorValido(string Valor) const { // si T es string no funcionará, tendrás que hacer una especialización std::istringstream BufI(Valor); T Tmp; BufI >> Tmp; std::ostringstream BufO; BufO << Tmp; return Valor==BufO.std(); } void AsignarValor(string Valor) { // si T es string no funcionará, tendrás que hacer una especialización std::istringstream Buf(Valor); Buf >> mValor; } string MostrarValor(void) { std::ostringstream Buf; Buf << mValor; return Buf.str(); } std::ostream &OutStream(std::ostream &Out) { return Out << mValor; } std::istream &InStream(std::istream &In) { // si T es string no funcionará, tendrás que hacer una especialización return In >> mValor; } campo *Clonar(void) const { t_campo<T> *pTmp = new t_campo<T>();
pTmp->mValor = mValor;
return pTmp;
} campo *Incrementar(const campo *Sumando) { try { mValor += dynamic_cast<T *>(Sumando)->mValor; } catch(...) { // error se ha intentado incremetar algo no incrementable } return this; } campo *Sumar(const campo *Sumando) const { return Clonar()->Incrementar(Sumando); } private: T mValor; t_campo(void); friend campo *campo::Crear(tipo Tipo); };Esto te funcionará para todos los tipos exepto para ALFANUN, si permites que un ALFANUM tenga espacios intercalados. Es decisión tuya. Pero en ese caso tendrás que hacer una especialización de t_campo<string>::AsignarValor y t_campo<string>InStream que funcionen.
Otro caso especial: Ten en cuenta que, por ejemplo, Incrementar sólo sabe incrementar DOUBLES con DOUBLES, si quieres que los DOUBLES se puedan incrementar con INT, tendras que hacer otra especialización para ese caso:
template<> campo *t_campo<double>::Incrementar(const campo *Sumando) { double Valor=0; switch (Sumando->mTipo) { case INT: Valor = dynamic_cast<int>(Sumando)->mValor; break; case DOUBLE: Valor = dynamic_cast<double>(Sumando)->mValor; break; case ALFANUM: { std::istringstream Buf(Sumando->MostrarValor()); Buf >> Valor; } break; // otros tipos que se puedan convertir a double default: throw; // Has intentado usar un campo no convertible a double } mValor += Valor; return this; }
Por el mismo precio, también has conseguido que un DOUBLE se pueda incrementar con un ALFANUN si es un string con la representación de un número, si no, sumará cero. Las funciones no son conmutativas. Si quieres que a un INT se le pueda incrementar un DOUBLE deberás hacer una especialización análoga. Resumiendo, tendrás que repasar el template para comprobar que todas las funciones funcionan bien para todos los tipos que utilices y, si una no funciona bien, hacer una especialización para esa función y tipo en concreto.
Ahora vamos con los constructores de los templates. Todos deberan ser especializaciones donde deberás poner las restricciones que quieras.
template<> t_campo<int>::t_campo<int>(void) :campo(INT) { mValor = 0; // valor defecto } template<> t_campo<string>::t_campo<string>(void) :campo(ALFANUM) { mValor = ""; }
Estos son dos ejemplos para INT y ALFANUM, para los otros tipos deberás hacer cosas parecidas.
Ahora la función creadora de campos, que es la función estática que definimos en la clase campo inicialmente y que implementaremos así:
campo *campo::Crear(tipo Tipo) { switch(Tipo) { case INT: return new t_campo<int>(); case DOUBLE: return new t_campo<double>(); //... default: throw; //error, tipo no definido } }Es el único sitio que tendrá un switch exhaustivo de todos los tipos de campos que quieras usar.
Una manera de usar la clase campo y sus derivadas es:
campo *p1 = campo::Crear(campo::INT); campo *p2 = campo::Crear(campo::INT); p2->AsignarValor("3"); campo *p3 = campo::Crear(campo::DOUBLE); p3->AsignarValor("78.32"); campo *p4 = p3->Sumar(p2); // p2 y p3 no varían cout << *p4 << "=" << *p3 << "+" << *p2; // escribirá 81.32=78.32+3 // si tecleas 98 cin >> *p1; // en p1 habrá 98 // si tecleas 'pepe' cin >> *p1; // en p1 habrá 0, ya que 'pepe' no es un INT y p1 espera un INT
Las últimas líneas te deben haber dado una pista de como leer un fichero para llenar un contenedor de campos. Basicamente sera algo parecido a esto:
campo::tipo Tipo; string NombreCampo; std::map<string,campo *> Contenedor; while (!In.eof()) { In >> NombreCampo >> Tipo; Contenedor[NombreCampo] = campo::Crear(Tipo); In >> *Contenedor[NombreCampo]; }Como ves, no es necesaria la función AsignarValor. Evidentemente, he supuesto que el fichero está bien formado, lo cual suele ser una presunción peligrosa.
Te dejo a tí todo el tratamiento de errores por si el fichero está mal formado y como pasar un contenedor a un fichero. Habrás observado que en campo está la función ValorValido, que dado un string te dice si contiene un valor valido para ese tipo de campo concreto. Te puede ser útil para detectar errores antes de que se produzcan. Estos ejemplos suponen que en los ficheros los datos se grabaran/leeran en formato legible por humanos. Si quieres trabajar con ficheros que tengan los datos mas compactos, tendrás que crear unas funciones virtuales nuevas, por ejemplo InHexFile y OutHexFile, que en cada clases derivadas sabrán como pasar de bytes a mValor y viceversa. Como siempre, tu decides.
Tambien falta la parte de limpiar la memoria. Hay news, pero ningún delete. En algún momento tendrás que hacerlos. Mirate boots::shared_ptr si te quieres olvidar de los deletes y que estos se hagan automáticamente, entoces tendrás que tener cuidado de otras cosas, tu mismo.
Finalmente: El código sólo está tecleado. No he intentado compilarlo. Por lo tanto puede que no sea estrictamente correcto.
Ha salido un mensaje muy largo, pero espero que las ideas estén lo suficientemente claras para que las puedas aprovechar.
Suerte.
-
martes, 21 de diciembre de 2010 9:23
Hola de nuevo Bartomeu.
En primer lugar agradecerte la ayuda, es indudable de que no has escatimado en tus respuestas, en particular esta ultima.
En cuanto al tema que nos ocupa; a ver, planteas cosas muy interesantes pero no es exactamente lo que yo busco. No pretendo (a priori) ni sumar campos ni meterlos ni sacarlos de un fichero, lo que pretendo es un objeto mas primitivo. Un objeto que a una petición del cliente, se "active" por decirlo de alguna forma y obtenga un dato (de momento de la consola) y lo guarde en su seno hasta que a petición del cliente, otra vez, devuelva el dato que tiene guardado. No necesita tener acceso a ningun fichero, ni sumarse a otro campo ni clonarse (de momento) ni nada por el estilo. Solo sirve a un propósito, obtener un dato (desde la consola) conservar ese dato y devolverlo cuando se le solicite.
Según lo que planteas, deberían devolver todos un string, o incluso, devolver un stringstream (o sus variantes istring.../ostring....). Bien, resumiendolo, así no me vale.
El objeto que utiliza el campo, quien solicita esas acciones de obtenerValor() y devolverValor(), no puede hacer suposiciones sobre el tipo de dato, ni conocer todos los tipos de datos que maneja, ... simplemente los tramita a otros (lo envia a un indice primario, o a uno secundario, para que hagan cosas con ese dato)por eso es importante que el "dato" a tramitar le llegue ya preparado sin requerir ninguna transformacion.
De no ser así dicho objeto tendría que transformar los valores recibidos antes de enviarlos a quien proceda, me explico.
Supongamos que el objeto que maneja los campos tiene un contenedor de campos, llamemosle Conte.
Bien, llegados a un punto, queremos que se carguen todos los campos con datos procedentes del usuario, pero antes, sabemos que hay un dato que es clave primaria, y queremos comprobar o no si existe en un indice, procedemos así.
Contenedor Conte; //supon un vector (por simpleza) con punteros a campos...
size_t n = 0, tam = Conte.size();
for(i; i<tam; i++ ) {
Conte[i]->obtenerValor(); //se encarga de que el usuario rellene todos los campos
//imagina que sabemos que el Conte[0] contiene una clave primaria
if( !i ) IndicePrimario->CompruebaClave( Conte[i]->devolverValor() );
Como verás, no se busca que Campo haga mucho mas que solo obtener valores del usuario y devolver dichos valores. Si campo me devolviera un stringstream entonces no podría hacer las cosas tal cual figura arriba IndicePrimario->CompruebaClave( .... en su lugar debería hacer algo parecido a lo siguiente:
if( !i ) { int tipo_dato = Conte[i]->devolverTipo();
switch( tipo_dato ) {
case CADENA:
string cad = Conte[i]->devolverValor(); //obvia los detalles de si es un string, stringstream...
IndicePrimario->(CompruebaClave(cad) );//obvia si devuelve un bool diciendo que existe o no
case ENTERO: int ent = Conte[i]->devolverValor(); // ....
case FLOAT: .... case DOUBLE: ... case LONG DOUBLE: case CADENAUNICODE: ...
En fin y hay mas tipos de datos con los que trabaja el diseño. A donde quiero llegar es que no es viable devolver un string, un stringstream o lo que sea, pues he de transformar el dato a un tipo determinado antes de ponerlo en el indice. Y ojo, esto solamente para buscar en un indicePrimario con el valor contenido en el campo Conte[ 0 ], .... pero hay mas campos, ... Conte[ 1 ], ... Conte[ n ]. Y para ellos tendría que añadir mas o menos lo mismo pero llamando algunas veces a IndiceSecundario( ....) etc...
Como ves, si obtengo el tipo de dato en su forma correcta la llamada es inmediata sin hacer suposiciones de ningun tipo sobre el dato devuelto por el campo
IndicePrimario->CompruebaClave( Conte[ i ]->devolverValor() );
IndiceSecundario->InsertaClave( Conte[ i ]->devolverValor() );
Estoy haciendo (como suelo hacer siempre) una prueba en modo consola. El campo que he creado,... joer y no veas si se ha complicado la cosa ( en esencia parecía un juego de niños, ... pero vaya tela ) he conseguido que haga todo lo que tiene que hacer, salvo por un pequeño detalle, utilizo (logicamente) un template para el valor de retorno de los campos
template<typename T> T devValor( string const& nombre_Campo );
El problema es: como no se pueden deducir los parametros de la plantilla por el tipo de los argumentos, que siempre es el mismo un string con el nombre del campo al cual acceder, he de instanciar explicitamente, esto es: Conte->devValor<string>( TITULO_LIBRO ), ... Conte->devValor<int>( PRECIO_LIBRO ), ... etc...
Pero hacerlo así de nuevo me obliga a hacer suposiciones sobre el tipo de los datos, swich( bla, bla, bla, ...).
Estoy buscando la forma de acceder al metodo devValor() sin tener que explicitamente indicar el tipo devValor<string>(), ... devValor<int>(), ... devValor<double>(), ... etc....
Logicamente, tendré que pasar alguna información a la función para que pueda deducir el argumento de su tipo de retorno. No me queda otra que pasar a la funcion template un int indicando el tipo de valor
template<int TIPO, typename T>
T devValor( string cont& nombre_Campo );
La cosa ahora es como hacer para que el segundo parámetro "T" se pueda deducir desde el primero "TIPO". Me he estado ojeando algo de programacion generica, y bueno, parece posible conseguir algo por el estilo, pero no parece una tarea sencilla a priori.
Buff, vaya tostón te he largado. Espero no obstante haber explicado cual es realmente el problema y que la solución de pasar un tipo de retorno no adecuado no parece una buena solución
Bueno Bartomeu, gracias de nuevo por tu colaboración, ... un saludo y felices fiestas para ti y todos los tuyos.
Roberto
-
miércoles, 22 de diciembre de 2010 14:12
Creo que sigues sin entender como se diseñan y usan en la práctica las funciones virtuales dentro de una jerarquia de clases.
Debes ser consciente que estas trabajando con dos niveles. En un nivel está la clase base, con sus funciones virtuales, que sólo sabe lo que tiene que hacer pero no como hacerlo, no sabe siquiera cuantas clases derivadas hay. En el otro nivel estan las clases derivadas que solo saben hacer lo suyo. Debes de empezar a pensar 'al reves'. Debes dejar de pensar en terminos de "hacer algo con un valor de la clase derivada" y pensar en terminos de "decirle a la clase base que le diga a la clase derivada que le haga algo al valor que guarda". A nivel de la clase base no sabemos, ni nos importa, el valor ni el tipo que guarda la clase derivada. Sólo sabemos lo que podemos/queremos hacer con él.
Veamos algunos ejemplos:
// no debes hacer IndicePrimario->CompruebaClave(Conte[i]->devolverValor()); IndiceSecundario->InsertaClave(Conte[i]->devolverValor()); // Debes hacer Conte[i]->CompruebaClave(IndicePrimario); Conte[i]->InsertaClave(IndiceSecundario); // En la clase base class campo { public: virtual bool CompruebaClave(indice *pInd)=0; virtual void InsertaClave(indice *pInd)=0; }; // En la clase derivada, que en este caso es un template template<class T> class t_campo :public campo { public: bool CompruebaClave(indice *pInd) {return pInd->CompruebaClave(mValor);} void InsertaClave(indice *pInd)=0; {pInd->InsertaClave(mValor);} private: T mValor; } // En la clase indice class indice { public: template<class T> bool CombruebaClave(const T &Valor) {return /*comprobar lo que haya que comprobar*/} template<class T> void InsertaClave(const T &Valor) {/*Insertar lo que haya que insertar*/} }; /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // si tienes que poner/sacar valores de un campo // en la consola, no debes hacer cin >> v; Conte[i]->PonerValor(v); cout << Conte[i]->obtenerValor(); // Debes hacer Conte[i]->CojerValerDe(cin); Conte[i]->PonerValorEn(cout); //en la clase base defines virtual istream &CojerValorDe(istream &In)=0; virtual ostream &PonerValorEn(ostream &Out)=0; // el la clase derivada implementas istream &CojerValorDe(istream &In) {return In >> mValor;} ostream &PonerValorEn(ostream &Out) {return Out << mValor;} // Y, si quieres ser mas elegante, fuera de las clases implementas inline istream &operator>>(istream &In, campo *pCampo) {return pCampo->CojerValorDe(In);} inline ostream &operator<<(ostream &Out, campo *pCampo) {return pCampo->PonerValorEn(Out);} // y podrás escribir directamente cin >> Conte[i]; cout << Conte[i]; ////////////////////////////////////////////////////// ////////////////////////////////////////////////////// // Resumiendo: si tienes que hacer algo que depende del tipo // del campo no debes hacer, !!BAJO NINGUN CONCEPTO!! int tipo_dato = Conte[i]->devolverTipo(); switch( tipo_dato ) { case CADENA: string cad = Conte[i]->devolverValor(); // Hacer algo con cad break; case ENTERO: int ent = Conte[i]->devolverValor(); // Hacer algo con ent break; case FLOAT: .... case DOUBLE: ... case LONG DOUBLE: case CADENAUNICODE // Debes hacer Conte[i]->HacerAlgo() // en la clase base virtual void HacerAlgo(void) =0; // En la clase derivada void HacerAlgo(void) {/*hacer lo necesario con mValor*/} //Si resulta que lo que hay que hacer con un tipo es muy dirente de lo //que hay que hacer con otro tipo, haces una especializacion del template template<> t_campo<int>::HacerAlgo(void) {/*cosas especiales con mValor sólo para el caso int*/} template<> t_campo<string>::HacerAlgo(void) {/*cosas especiales con mValor sólo para el caso string*/}
Es decir, cuando dices que un campo "Solo sirve a un propósito, obtener un dato (desde la consola) conservar ese dato y devolverlo cuando se le solicita" estás equivocado. Un campo debe hacer TODO lo que pueda hacer un campo y TODO lo que le puedan querer hacer que haga. Cuando trabajes a nivel de la clase base no necesitas saber de que tipo es, sólo debes preocuparte de pasarle todos los datos que necesitará la clase derivada para hacer lo que tenga que hacer. La clase derivada ya sabe de que tipo es y sabrá actuar en conscuencia.
Como has visto en los ejemplos que te he puesto no tienes que deducir los tipos de los campos cuando trabajes con la clase base, ya que esta se limita a llamar a la clase derivada y esta ya sabe de que tipo es. En la clase derivada, T siempre está definido y será lo que sea: int, double, string...
Espero que esté lo suficientemente claro.
También te deseo que pases unas felices fiestas.
-
martes, 28 de diciembre de 2010 7:07
Hola de nuevo Bartomeu.
Pues si, puede ser que esté subestimando el polimorfismo dinámico, aunque no es tan sencillo. Verás he programado unos arboles B+ (no recursivos), unos indices que utilizan tales arboles, unas tablas que manejan varios de esos indices cada una y logicamente hay un núcleo que es quien se encarga de despachar todas las peticiones y dejarselas a quien corresponde. No puedo hacer exactamente lo que dices, pues un objeto tan simple como un campo no puede tener acceso directo a las tablas, pero si es cierto que podría comunicarse con dicho nucleo. Tendré que echar un vistazo general a mi diseño y cambiar algunas cosas, ...estoy en ello.
Gracias de nuevo por tu desinteresada y útil ayuda, ya te comentaré algo sobre el rumbo que he tomado. Aprovecho la ocasión para desearte un fabuloso 2011 a ti y a todos los tuyos.
Roberto
-
martes, 28 de diciembre de 2010 14:59
Date cuenta que en mis ejemplos, campo no tiene acceso directo a ningún sitio ni a nadie.
Las funciones virtuales de campo solo proporcionan una interface para llamar a quien corresponga con los argumento perfectamente tipados en la clase derivada y, a nivel de la clase derivada, tienes exactamente el mismo acceso que tenias antes de llamar a la clase virtual, ni mas ni menos. Por ejemplo:
//Debes buscar cualquier instrucción con la estructura: Alguien->HaceAlgoCon(Conte[i]->DevolverValor()/*,mas posibles argumentos*/); // Y cambiarlo por Conte[i]->SeDejaHacerAlgoPor(Alguien/*,mas posibles argumentos*/); // En la clase base defines virtual void SeDejaHacerAlgoPor(alguien *pAlguien/*,mas posibles argumentos*/)=0; // En el template derivado implementas void SeDejaHacerAlgoPor(alguien *pAlguien/*,mas posibles argumentos*/) { // Aqui mValor es claramente un int, o un double o un string o... // y puede hacer finalmente la llamada original sin ambigüedades pAlguien->HaceAlgoCon(mValor/*,mas posibles argumentos*/); }
Por consiguiente, campo no puede ser tan simple, debe proporcionar las interfaces para hacer todo lo que se le pueda hace a un campo.
Suerte con los cambios en el diseño. Pero son cambios menores. Sólo tienes que cambiar la manera de hacer las cosas, pensando al 'reves'. Pasar de 'HacerAlgoCon' a 'MeDejoHacerAlgoPor' y definir una función virtual que finalmente llame a la original 'HacerAlgoCon', que no tienes que variar.
Suerte.

