none
Duda con Plantillas[C++] RRS feed

  • Pregunta

  • Hola a todos!

    Tengo una duda acerca de las templates en C++.
    Despues de leer varios tutoriales, por fin pude crear por mi cuenta una Lista Simplemente Enlazada utilizando plantillas y dos clases(Nodo y Lista).

    Mi pregunta es:
    ¿Por que cuando tengo todo mi código junto en un solo archivo, cuando compilo y corro mi programa funciona perfecto, pero cuando separo el codigo en archivos de cabecera(.h y .cpp) mas el main.cpp mi compilador(g++) al momento del enlazado me tira varios errores?

    Mi codigo TODO JUNTO el que si funciona es:

    #include <iostream>
    using namespace std;
    
    template <class Q>
    class Nodo{
    	private:
    		Q *MiDato;
    		Nodo<Q> *next;
    	public:
    		Nodo(Q nuevo){
    			next = NULL;
    			MiDato = new Q;
    			*MiDato = nuevo;
    		}
    		Nodo(void): MiDato(NULL), next(NULL) {}
    		~Nodo(void){
    			delete MiDato;
    			next = NULL;
    		}
    		void set_data(Q nuevo){
    			MiDato = new Q;
    			*MiDato = nuevo;
    		}
    		void set_next(Nodo<Q> *s) { next = s; }
    		const Q get_data(void) const { return *MiDato; }
    		Nodo<Q> *get_next(void) { return next; }
    		void ShowAll(void) const{
    			Nodo<Q> *aux = next;
    
    			cout << *MiDato << endl;
    			while(aux){
    				cout << aux->get_data() << endl;
    				aux = aux->get_next();
    			}
    		}
    };
    
    template <class Q>
    class Lista{
    	private:
    		Nodo<Q> *inicio;
    		Nodo<Q> *final;
    		int tam;
    	public:
    		Lista(void): inicio(NULL), final(NULL), tam(0) {}
    		~Lista(void){
    			Nodo<Q> *aux = inicio;
    
    			if(!empty()){
    				while(inicio){
    					inicio = inicio->get_next();
    					delete aux;
    					aux = inicio;
    				}
    			}
    			final = NULL;
    			tam = 0;
    		}
    		void add(Q nuevo){
    			Nodo<Q> *aux = new Nodo<Q>(nuevo);
    			if(empty()){
    				inicio = aux;
    			}else{
    				final->set_next(aux);
    			}
    			final = aux;
    			tam++;
    		}
    		int get_tam(void) const { return tam; }
    		bool empty(void) const{
    			return (inicio == NULL);
    		}
    		void Mostrar(void) const { inicio->ShowAll(); }
    };
    
    int main(void){
    	Lista<int> Buena;
    	Lista<char> Mala;
    
    	for(int i = 10; i < 21; i++){
    		Buena.add(i);
    	}
    	Mala.add('M');
    	Mala.add('A');
    	Mala.add('L');
    	Mala.add('A');
    
    	cout << "Buena mide: " << Buena.get_tam() << endl;
    	Buena.Mostrar();
    	cout << "\nMala mide: " << Mala.get_tam() << endl;
    	Mala.Mostrar();
    
    	return 0;
    }
    


    Y el error que me tira si creo los siguientes 5 archivos:
    main.cpp, Nodo.cpp, Nodo.h, Lista.cpp y Lista.h
    es:

    $ g++ -c Nodo.cpp
    $ g++ -c Lista.cpp
    $ g++ main.cpp *.o
    /tmp/ccNbYLWk.o: In function `main':
    main.cpp:(.text+0x13): undefined reference to `Lista<int>::Lista()'
    main.cpp:(.text+0x1f): undefined reference to `Lista<char>::Lista()'
    main.cpp:(.text+0x39): undefined reference to `Lista<int>::add(int)'
    main.cpp:(.text+0x59): undefined reference to `Lista<char>::add(char)'
    main.cpp:(.text+0x6a): undefined reference to `Lista<char>::add(char)'
    main.cpp:(.text+0x7b): undefined reference to `Lista<char>::add(char)'
    main.cpp:(.text+0x8c): undefined reference to `Lista<char>::add(char)'
    main.cpp:(.text+0x98): undefined reference to `Lista<int>::get_tam() const'
    main.cpp:(.text+0xcc): undefined reference to `Lista<int>::Mostrar() const'
    main.cpp:(.text+0xd8): undefined reference to `Lista<char>::get_tam() const'
    main.cpp:(.text+0x10c): undefined reference to `Lista<char>::Mostrar() const'
    main.cpp:(.text+0x11d): undefined reference to `Lista<char>::~Lista()'
    main.cpp:(.text+0x130): undefined reference to `Lista<char>::~Lista()'
    main.cpp:(.text+0x144): undefined reference to `Lista<int>::~Lista()'
    main.cpp:(.text+0x160): undefined reference to `Lista<int>::~Lista()'
    collect2: ld devolvió el estado de salida 1
    $
    


    ¿Alguien me puede explicar que  sucede?
    jueves, 30 de diciembre de 2010 4:15

Respuestas

  • Me alegro de haberte sido útil. En cuanto a tu pregunta sobre bibliotecas pre-compiladas, entiendo que te refieres a crear OBJs para meter en una LIB, hay tres posibles respuestas:

    a) La corta: No se pueden crear OBJ de templates.

    b) La larga: Depende del compilador y de las opciones que escogas en algunos casos se pueden crear OBJ para cada Q que especifiques expresamente. Consulta el manual de g++ . Incidentalmente, en la nueva versión de C++ (la C++x0 que tenía que salir el año pasado y que ya veremos si sale el año próximo) decían que ivan a normalizar esto de los templates para que todos los compiladores los trataran igual, pero hasta que lo aprueben y los compiladores lo implementen pasará un tiempo.

    c) La complicada: Puedes usar un subterfugio consistente en crearte una clase base de Nodo<Q> y de Lista<Q>, por ejemplo Nodo_void y Lista_void, que se encargen de tratar todo los enlaces como punteros void, entonces pasas a estas clases base todas las funciones que puedas y que sean independientes del tipo Q, y reservas los templates para que sean unos meros interfaces que pasen de punteros Q a punteros void y viceversa. Nodo_void y Lista_void los podras poner sin dificultad en un OBJ. Pero en tu caso, y dada la simplicidad de tu implementación, creo que sería excesivo intentar esta solución, si no es como un mero ejercicio para conocer la técnica. Tu mismo. Releyendo tu código para el posible uso de este método me he dado cuento que tienes una fuga de memoria en Nodo<Q>::set_data, creas un nuevo puntero machacando el puntero anterior que guardabas en MiDato, deberias hacer primero un delete o mejor, ni siquiera hagas el new, haz la asignación directamente.

    En cuanto a las ventajas/desventajas de los templates, son simples. Desventajas, el compilador genera más código del aparente. Ventajas, el programador escribe menos código y confía en que el compilador creará el codigo necesario para cada tipo optimizándolo para cada tipo Q en concreto. Hay que tener confianza en el compilador.

    • Marcado como respuesta Karitelis jueves, 30 de diciembre de 2010 19:45
    jueves, 30 de diciembre de 2010 12:38

Todas las respuestas

  • EL compilador necesita tener el código que implementa la función siempre visible para crear la versión personalizada al tipo de Q sobre la marcha cuando la necesita.

    Cuando separas .h y .cpp y en main.cpp haces los  includes de los .h, el compilador no sabe como generar el cuerpo personalizado ni a char ni a int, y por lo tanto quedan indefinidos. Por eso protesta.

    Hay compiladores que tienen opciones para poder hacer eso en algunos casos y de alguna manera. No conozco g++ y no te puedo ayudar en ese punto. Pero la solución estandar es poner en el mismo .h de la plantilla todas las definiciones y las implementaciones, ya sea como funciones inline, si son cortas, o despues de la definición de la clase si son más complicadas, dejando la definición de la clase más limpia.

    En tu caso tienes otra solución, que yo utilizo cuando las funciones del template son muchas y/o muy grandes. Renombra los ficheros Nodo.cpp y Lista.cpp como Nodo.hpp y Lista.hpp y AL FINAL de Nodo.h y Lista.h, antes del #endif, haces un include de los ficheros .hpp respectivos. Tendrás unos ficheros .h pequeños, manejables y leibles y el compilador siempre tendrá acceso a la implementación de todas las funciones.

     

    jueves, 30 de diciembre de 2010 6:47
  • OK Mil gracias Bartomeu!!

     

    Probé con las dos formas que me dijiste y efectivamente las dos funcionaron...

    La pregunta entonces es:

    Al usar bibliotecas de plantillas, ¿Estas bibliotecas no pueden ser pre-compiladas?

    De esto me di cuanta por que si quiero hacer archivos objeto de mis cabeceras, esto ya no es posible.

    Al tener mis 3 archivos main.cpp, Nodo.h y Lista.h, el ejecutable lo creo con un

    $g++ main.cpp

    ¿Que desventajas tiene esto?

    jueves, 30 de diciembre de 2010 9:20
  • Te contesté a tu problema sin haberme leido tu código. Ahora ya lo he leido y, si aceptas algunas opiniones, sigue leyendo, pero antes de nada, dado que el código funciona, recuerda que no tienes por qué hacerme caso. Es tu código y tú mandas.

    1º En Nodo::ShowAll(void) cambiaria las dos lineas antes del while(aux) por una sola línea Nodo<Q> *aux=this; Con sólo el cout que está dentro del while saldrán todos los nodos.

    2º El nombre ShowAll es equívoco. Esta función sólo lista TODOS los nodos si la llamas desde el primer nodo, si la llamas desde el quinto, sólo te listará desde el quinto al final. Yo cambiaria el nombre por ShowThisAndNexts o cualquier otro nombre que indique lo que hace en realidad llame a la funcion quien la llame y desde donde quiera.

    3º Es una cuestión de concepto. Creo que un nodo sólo se tendría que preocupar de el mismo y de quién es su hermano inmediato. No tiene por qué ver más allá. Si alguna función tiene que recorrer varios nodos debería estar implementada en Lista, no en Nodo. Por lo tanto ShowAll la trasladaria integra dentro de Lista<Q>::Mostrar 

    4º En Lista<Q>::~Lista(void) sobra el if(!empty()) si la lista está vacia while(incio) ya no se ejecutará.

    Esto es lo más evidente que he visto. Pero recuerda, son simples opiniones que puedes ignorar ya que tu código funciona.

    Te recomiendo que te leas el fichero <list> de la STL, verás cógigo realmente curioso y eficiente.

    jueves, 30 de diciembre de 2010 9:20
  • Me alegro de haberte sido útil. En cuanto a tu pregunta sobre bibliotecas pre-compiladas, entiendo que te refieres a crear OBJs para meter en una LIB, hay tres posibles respuestas:

    a) La corta: No se pueden crear OBJ de templates.

    b) La larga: Depende del compilador y de las opciones que escogas en algunos casos se pueden crear OBJ para cada Q que especifiques expresamente. Consulta el manual de g++ . Incidentalmente, en la nueva versión de C++ (la C++x0 que tenía que salir el año pasado y que ya veremos si sale el año próximo) decían que ivan a normalizar esto de los templates para que todos los compiladores los trataran igual, pero hasta que lo aprueben y los compiladores lo implementen pasará un tiempo.

    c) La complicada: Puedes usar un subterfugio consistente en crearte una clase base de Nodo<Q> y de Lista<Q>, por ejemplo Nodo_void y Lista_void, que se encargen de tratar todo los enlaces como punteros void, entonces pasas a estas clases base todas las funciones que puedas y que sean independientes del tipo Q, y reservas los templates para que sean unos meros interfaces que pasen de punteros Q a punteros void y viceversa. Nodo_void y Lista_void los podras poner sin dificultad en un OBJ. Pero en tu caso, y dada la simplicidad de tu implementación, creo que sería excesivo intentar esta solución, si no es como un mero ejercicio para conocer la técnica. Tu mismo. Releyendo tu código para el posible uso de este método me he dado cuento que tienes una fuga de memoria en Nodo<Q>::set_data, creas un nuevo puntero machacando el puntero anterior que guardabas en MiDato, deberias hacer primero un delete o mejor, ni siquiera hagas el new, haz la asignación directamente.

    En cuanto a las ventajas/desventajas de los templates, son simples. Desventajas, el compilador genera más código del aparente. Ventajas, el programador escribe menos código y confía en que el compilador creará el codigo necesario para cada tipo optimizándolo para cada tipo Q en concreto. Hay que tener confianza en el compilador.

    • Marcado como respuesta Karitelis jueves, 30 de diciembre de 2010 19:45
    jueves, 30 de diciembre de 2010 12:38
  • Mil gracias!
    No me habia dado cuenta de lo que hacia con set_data().
    Y en cuanto a Nodo<Q> *aux = this si me di cuenta y lo había hecho como tu me aconsejas pero el compilador me devolvía esto:
    Nodo.cpp:35: error: conversión inválida de ‘const Nodo<char>* const’ a ‘Nodo<char>*’
    Así que lo cambie y lo deje tal como esta publicado... No lo entiendo(debería funcionar), tal vez haga falta un cast.
    Excepto de esa observación, te hice caso en las demás y ya corregí mi código MUCHAS GRACIAS!!

    Y bueno me quedaré con la respuesta a) y en espera de la b)
    =)

    Una vez mas muchísimas gracias me has ayudado bastante y aclarado una duda que ya llevaba dias en mi cabecita.
    jueves, 30 de diciembre de 2010 19:44
  • No le hace falta un cast, le falta un const. Ten en cuenta que this es const, por consiguiente aux tambien debe serlo. Escribe const Nodo<Q> *aux = this. Ahora si que debería funcionar.

    jueves, 30 de diciembre de 2010 21:29