Undefined Reference,templates e overloading dell'operatore()

di
Anonimizzato6415
il
4 risposte

Undefined Reference,templates e overloading dell'operatore()

Ave,
sono nuovo del forum, ho cercato in giro per questo alla ricerca di una soluzione ma non ho trovato nulla in ogni caso vi chiedo scusa se ho fatto un doppio post (non è facile capire gli errori dei compilatori C++).

Vi spiego il mio problema:
devo gestire una matrice multidimensionale (per ora mi sono limitato alle matrici bidimensionali) e per fare ciò ho preso spunto dal C++ Faq Lite () ma invece che quel codice ho voluto fare una cosa un po' più complessa ovvero creare una interfaccia Matrix e due implementazioni: ArrayMatrix e VectorMatrix (e in futuro vorrei aggiungerne altre).

Credo che il codice sia sintatticamente giusto ma l'errore che mi ritorna il compilatore quando utilizzo una delle due implementazioni è criptico (come sempre):

**** Build of configuration Debug for project testVari ****

make all
Building target: testVari
Invoking: GCC C++ Linker
g++  -o"testVari"  ./src/ArrayMatrix.o ./src/TestSingleton.o ./src/VectorMatrix.o ./src/testVari.o  
./src/testVari.o: In function `main':
./Debug/../src/testVari.cpp:43: undefined reference to `ArrayMatrix<Prova>::ArrayMatrix(unsigned int, unsigned int)'
./Debug/../src/testVari.cpp:44: undefined reference to `ArrayMatrix<Prova>::operator()(unsigned int, unsigned int)'
./Debug/../src/testVari.cpp:45: undefined reference to `ArrayMatrix<Prova>::operator()(unsigned int, unsigned int)'
./Debug/../src/testVari.cpp:84: undefined reference to `ArrayMatrix<Prova>::~ArrayMatrix()'
./Debug/../src/testVari.cpp:84: undefined reference to `ArrayMatrix<Prova>::~ArrayMatrix()'
collect2: ld returned 1 exit status
make: *** [testVari] Error 1
Il progetto è stato creato con Eclipse e i makefile li gestisce (abbastanza bene devo dire) l'IDE.

Vi posto il codice che ho scritto:

Matrix.h:

/*
 * Matrix.h
 *
 *  Created on: 15/ago/2009
 *      Author: blueangel
 */

#ifndef MATRIX_H_
#define MATRIX_H_
#include "Eccezione.h"

template<typename T> class Matrix {
public:
    Matrix(unsigned int rows, unsigned int cols) throw (Eccezione) : _righe(rows), _colonne(cols) {}
    virtual ~Matrix();
    virtual T* operator()(unsigned int row, unsigned int col) throw (Eccezione) = 0;
    virtual void purge(unsigned int row, unsigned int col) throw (Eccezione) = 0;
    virtual const T* pop(unsigned int row, unsigned int col) throw (Eccezione) = 0;
private:
    const T& operator[](int);
protected:
    unsigned int _righe;
    unsigned int _colonne;
};

class BadSize : public Eccezione
{
public:
    BadSize(const std::string& messaggio) : Eccezione(messaggio) {}
};

class IndexOutOfBound : public Eccezione
{
public:
    IndexOutOfBound(const std::string& messaggio) : Eccezione(messaggio) {}
};
#endif /* MATRIX_H_ */
Eccezione.h

/*
 * Eccezione.h
 *
 *  Created on: 25-mag-2009
 *      Author: r.riccobene
 */

#ifndef MOUNTEXCEPTION_H_
#define MOUNTEXCEPTION_H_
#include <exception>
#include <string>

using std::string;

class Eccezione : public std::exception {
public:
    Eccezione();
    Eccezione(const string& messaggio) : errore(messaggio) {}
    ~Eccezione() throw() {}

    const char* what() const throw() { return errore.c_str(); }
protected:
    string errore;
};

#endif /* MOUNTEXCEPTION_H_ */
ArrayMatrix.h

/*
 * ArrayMatrix.h
 *
 *  Created on: 15/ago/2009
 *      Author: blueangel
 */

#ifndef ARRAYMATRIX_H_
#define ARRAYMATRIX_H_
#include "Matrix.h"

template<typename T> class ArrayMatrix : public Matrix<T>
{
public:
    ArrayMatrix(unsigned int rows, unsigned int cols) throw (Eccezione);
    ~ArrayMatrix();
    T* operator()(unsigned int row, unsigned int col) throw (Eccezione);
    void purge(unsigned int row, unsigned int col) throw (Eccezione);
    const T* pop(unsigned int row, unsigned int col) throw (Eccezione);
private:
    T* _data;
};

#endif /* ARRAYMATRIX_H_ */
ArrayMatrix.cpp

/*
 * ArrayMatrix.cpp
 *
 *  Created on: 15/ago/2009
 *      Author: blueangel
 */

#include "ArrayMatrix.h"

template<typename T>ArrayMatrix<T>::ArrayMatrix(unsigned int rows, unsigned int cols)
    throw (Eccezione) : Matrix<T>(rows, cols)
{
    if (rows == 0 || cols == 0)
        throw BadSize("Impossile istanziare una Matrice di 0 righe o colonne.\n");
    _data = new T[Matrix<T>::_righe * Matrix<T>::_colonne];
}

template<typename T> T*ArrayMatrix<T>::operator()(unsigned int row, unsigned int col) throw (Eccezione)
{
    if (row > Matrix<T>::_righe || col > Matrix<T>::_colonne)
        throw IndexOutOfBound("Indice di riga o colonna non valido");
    return _data[row * Matrix<T>::_colonne + col];
}

template<typename T> void ArrayMatrix<T>::purge(unsigned int row, unsigned int col) throw (Eccezione)
{
    T* p = this->operator ()(row, col);
    delete p;
}

template<typename T> const T* ArrayMatrix<T>::pop(unsigned int row, unsigned int col) throw (Eccezione)
{
    if (row > Matrix<T>::_righe || col > Matrix<T>::_colonne)
            throw IndexOutOfBound("Indice di riga o colonna non valido");
    const T* retval = _data[row * Matrix<T>::_colonne + col];
    _data[row * Matrix<T>::_colonne + col] = NULL;
    return retval;
}

template<typename T> ArrayMatrix<T>::~ArrayMatrix()
{
    delete[] _data;
}
e il file di test col main: (testVari.cpp)

#include <iostream>
#include <string.h>
#include <sstream>
#include "ArrayMatrix.h"

using namespace std;

static const string test = "test";

string stringhifica(int valore);

class Prova
{
public:
    Prova()
    {
        static int i = 0;
        myI = i++;

        string test = "Test n.: ";
        cout << test << stringhifica(myI) << "\n";
    }

    inline const int getMyI() const { return myI; }
private:
    int myI;
};

int main()
{
    ArrayMatrix<Prova> matrice(5,4);
    Prova* p1 = matrice(0,0);
    Prova* p2 = matrice(2,3);
    p1 = new Prova();
    p2 = new Prova();


    cout << "Ok, ora li riprendo:\n";
    //int valore = p2->getMyI();
    //cout << "E il valore ritornato è: " << stringhifica(valore) << "\n";
    return 0;
}

string stringhifica(int valore)
{
    ostringstream o;
    o << valore;
    return o.str();
}
Vi ringrazio in anticipo per le risposte.

4 Risposte

  • Re: Undefined Reference,templates e overloading dell'operatore()

    Wow, un sacco di letture e nessuna risposta.

    Temo che con questo codice ho beccato le sfige peggiori del C++.
    A parte che ci sarebbe da frustare a sangue le mani del programmatore che ha scritto la parte degli errori del compilatore (perchè, per zeus, se non va un template mi dai un errore di linking generico?) poi non sono stato con le mani in mano.

    Ho scoperto tante belle cose, anzitutto il problema riguarda i template in generale e non la ridefinizione dell'operatore come pensavo io.
    Poi il problema è che separare dichiarazione e definizione dei template provoca disastri immani, catastrofi naturali, nausea, vomito e diarrea.

    Bene, secondo C++ Faq Lite (vedi post precedente) e B.Stroustrup separare i template in due files (header e file cpp) provoca problemi in quanto il compilatore non sa bene come gestire un template perchè per poterlo compilare e linkare a dovere ha bisogno di conoscere la sua implementazione.

    Le soluzioni quindi sono varie: fare un unico e quindi usare dichiarazione e definizione senza separarli in un header e in un file cpp sembra essere la più adatta. Lo svantaggio è che chi usa il file vede l'implementazione, la compilazione rallenta e sopratutto l'header diventa mostruoso.

    Mettiamo che ci sia qualcuno testardo (tipo me) che vuole comunque provare a separare dichiarazione e definizione.
    Bene, per i template esiste una keyword ovvero export che serve proprio per la separazione dichiarazione/definizione dei template. Fantastico no? No: la keyword non è supportata da quasi nessun compilatore e il comitato dello standard C++ non la ha mai definito per bene e quindi è inutile (ho letto che forse introdurranno un sistema simile nel prossimo standard C++ 0x).

    Secondo Stroustrup una soluzione è quella di inserire una dichiarazione di specializzazione di template nel file cpp per forzare il compilatore a inserire header e file cpp in una unica unità di compilazione... ovvero aggiungere, per esempio nella mio file ArrayMatrix.cpp, la dichiarazione:
    
    template class ArrayMatrix<int>;
    
    In questo modo gli errori sui template sono spariti.
    Daccordo, c'erano una marea di altri errori (che ho corretto) e credo di aver perso un bello scivolone col metodo pop() (mi sa che quel che volevo fare non si può fare, non così almeno).

    Tralasciando questi inconvenienti ho rimodificato i vari files togliendo alcune cose e rendendo il codice più simile a quello del C++ Faq-Lite e facendo volare il comportamento di pop() che per ora non mi serve.
    Bene, adesso non ho più tutti gli errori su ArrayMatrix!
    Aleeee!
    Adesso me ne spuntano invece altri su Matrix.h -_-
    Decisamente io e i template abbiamo litigato.

    Vi posto l'errore e i files modificati:
    
    Building target: testVari
    Invoking: GCC C++ Linker
    g++  -o"testVari"  ./src/ArrayMatrix.o ./src/TestSingleton.o ./src/VectorMatrix.o ./src/testVari.o   
    ./src/testVari.o: In function `~ArrayMatrix':
    /mnt/data/opt/workspace/testVariC++/Debug/../src/ArrayMatrix.cpp:60: undefined reference to `Matrix<Prova>::~Matrix()'
    /mnt/data/opt/workspace/testVariC++/Debug/../src/ArrayMatrix.cpp:60: undefined reference to `Matrix<Prova>::~Matrix()'
    ./src/testVari.o: In function `ArrayMatrix':
    /mnt/data/opt/workspace/testVariC++/Debug/../src/ArrayMatrix.cpp:23: undefined reference to `Matrix<Prova>::~Matrix()'
    ./src/testVari.o:(.rodata._ZTV6MatrixI5ProvaE[vtable for Matrix<Prova>]+0x8): undefined reference to `Matrix<Prova>::~Matrix()'
    ./src/testVari.o:(.rodata._ZTV6MatrixI5ProvaE[vtable for Matrix<Prova>]+0xc): undefined reference to `Matrix<Prova>::~Matrix()'
    collect2: ld returned 1 exit status
    make: *** [testVari] Error 1
    
    Matrix.h:
    
    #ifndef MATRIX_H_
    #define MATRIX_H_
    #include "Eccezione.h"
    template<typename T> class Matrix {
    public:
    	Matrix(unsigned int rows, unsigned int cols) throw (Eccezione)
    		: _righe(rows), _colonne(cols) {}
    	virtual ~Matrix();
    	virtual T& operator()(unsigned int row, unsigned int col) throw (Eccezione) = 0;
    	virtual void purge(unsigned int row, unsigned int col) throw (Eccezione) = 0;
    	virtual T& pop(unsigned int row, unsigned int col) throw (Eccezione) = 0;
    private:
    	const T& operator[](int);
    protected:
    	unsigned int _righe;
    	unsigned int _colonne;
    };
    class BadSize : public Eccezione
    {
    public:
    	BadSize(const std::string& messaggio) : Eccezione(messaggio) {}
    };
    class IndexOutOfBound : public Eccezione
    {
    public:
    	IndexOutOfBound(const std::string& messaggio) : Eccezione(messaggio) {}
    };
    #endif /* MATRIX_H_ */
    
    
    (Eccezione.h è rimasto inalterato)

    ArrayMatrix.h:
    
    /*
     * ArrayMatrix.h
     *
     *  Created on: 15/ago/2009
     *      Author: blueangel
     */
    
    #ifndef ARRAYMATRIX_H_
    #define ARRAYMATRIX_H_
    #include "Matrix.h"
    
    template<typename T> class ArrayMatrix : public Matrix<T>
    {
    public:
    	inline ArrayMatrix(unsigned int rows, unsigned int cols) throw (Eccezione);
    	inline ~ArrayMatrix();
    	inline T& operator()(unsigned int row, unsigned int col) throw (Eccezione);
    	inline void purge(unsigned int row, unsigned int col) throw (Eccezione);
    	inline T& pop(unsigned int row, unsigned int col) throw (Eccezione);
    private:
    	T* _data;
    };
    
    ArrayMatrix.cpp:
    [code]
    #include "ArrayMatrix.h"
    
    template<typename T>ArrayMatrix<T>::ArrayMatrix(unsigned int rows, unsigned int cols)
    	throw (Eccezione) : Matrix<T>(rows, cols)
    {
    	if (rows == 0 || cols == 0)
    		throw BadSize("Impossile istanziare una Matrice di 0 righe o colonne.\n");
    	_data = new T[Matrix<T>::_righe * Matrix<T>::_colonne];
    }
    
    template<typename T> T& ArrayMatrix<T>::operator()(unsigned int row, unsigned int col) throw (Eccezione)
    {
    	if (row > Matrix<T>::_righe || col > Matrix<T>::_colonne)
    		throw IndexOutOfBound("Indice di riga o colonna non valido");
    
    	return _data[row * Matrix<T>::_colonne + col];
    }
    
    template<typename T> void ArrayMatrix<T>::purge(unsigned int row, unsigned int col) throw (Eccezione)
    {
    	T p = this->operator ()(row, col);
    	delete &p;
    }
    
    template<typename T> T& ArrayMatrix<T>::pop(unsigned int row, unsigned int col) throw (Eccezione)
    {
    	if (row > Matrix<T>::_righe || col > Matrix<T>::_colonne)
    			throw IndexOutOfBound("Indice di riga o colonna non valido");
    
    	T& retval = _data[row * Matrix<T>::_colonne + col];
    	//_data[row * Matrix<T>::_colonne + col] = NULL;
    	return retval;
    }
    
    template<typename T> ArrayMatrix<T>::~ArrayMatrix()
    {
    	delete[] _data;
    }
    
    #endif /* ARRAYMATRIX_H_ */
    [/code]

    ed infinte testVari.cpp:
    
    #include <iostream>
    #include <string>
    #include <fstream>
    #include <sstream>
    #include "ArrayMatrix.h"
    #include "ArrayMatrix.cpp"
    
    using namespace std;
    
    static const string test = "test";
    
    string stringhifica(int valore);
    
    class Prova
    {
    public:
    	Prova()
    	{
    		static int i = 0;
    		myI = i++;
    
    		string test = "Test n.: ";
    		cout << test << stringhifica(myI) << "\n";
    	}
    
    	inline const int getMyI() const { return myI; }
    private:
    	int myI;
    };
    
    int main()
    {
    	ArrayMatrix<Prova> matrice(5,4);
    	Prova p1 = matrice(0,0);
    	Prova p2 = matrice(2,3);
    
    	cout << "Ok, ora li riprendo:\n";
    	int valore = p2.getMyI();
    	//cout << "E il valore ritornato è: " << stringhifica(valore) << "\n";
    	return 0;
    }
    
    string stringhifica(int valore)
    {
    	ostringstream o;
    	o << valore;
    	return o.str();
    }
    
    Potete vedere dal codice che ho unito un po' di soluzioni: quella di Stroustroup (non ho il libro per mano, comunque capitolo 16: Templates) e quella del C++ Faq Lite.
    Stroustroup dice di inserire una dichiarazione di specializzazione del template (template class ArrayMatrix<int>) mentre C++ Faq Lite di rendere i metodi inline.
    Temo pure di aver travisato: mi sa che Stroustroup intendesse di inserire la dichiarazione di specializzazione per OGNI utilizzo possibile del template... spero non sia così altrimenti a ******* i template, usati così non servono a una mazza.

    Altrettanto col C++ Faq Lite: ho reso inline tutto.

    Vedete l'include a ArrayMatrix.cpp? Beh questa l'ho letta in un forum (perso il link, mi dispiace), ebbene con questa include l'errore lo da su Matrix mentre se lo tolgo mi da gli errori che vi ho scritto nel post di prima.

    In sintesi: come diamine uso i template?

    Mi piace ancora il C++, è grandioso, ma non ho così tanto tempo da perdere in seghe mentali.
  • Re: Undefined Reference,templates e overloading dell'operatore()

    Ok, grazie lo stesso a tutti, ho scoperto come mai non va.
    Template e funzioni virtuali non vanno daccordo -_-

    Un template non può essere virtuale. E che zeus...
    Bhe, a questo punto il problema cambia visto che mi trovo di fronte a un problema di design: vorrei una interfaccia che però non può essere template ma ho bisogno che la classe che la implementa sia un template.

    A questo punto la domanda diventa: è possibile? Posso fare una interfaccia Matrix che dia dei metodi comuni e una implementazione ArrayMatrix che sia template?

    Il cruccio principale è sull'overriding dell'operatore() ovvero se la classe Matrix la trasformo in una non template la dichiarazione di questo metodo diventa
    
    virtual Matrix& operator()(unsigned int row, unsigned int col);
    
    e quindi implementandola devo ritornare un valore Matrix (rendendo inutile il template), giusto?
    Qualcuno ha da propormi qualche soluzione?
  • Re: Undefined Reference,templates e overloading dell'operatore()

    Ho scoperto che il problema, come sempre quando ne becco uno nel C++, è tutt'altro che quello che pensi o ti suggerisce il compilatore.

    Anzitutto avevo letto in giro che i template non possono essere virtuali e con questa scoperta ho deviato pensando che non avrei potuto mantenere la mia interfaccia ma invece mi hanno dimostrato in un altro forum (scusate se mi sono rivolto alla concorrenza ) che invece questo non è vero (i template possono essere virtuali).

    E infatti il problema era un altro: il distruttore della classe Matrix era virtuale puro e questo generava errori di compilazione. Reso virtuale e datogli una implementazione (vuota) il codice si è messo a funzionare.

    In questo link: spiegano che un distruttore può essere reso virtuale puro ma bisogna comunque dagli una implementazione.

    Ora posso continuare con queste maledette matrici.
    Ciao a tutti.
  • Re: Undefined Reference,templates e overloading dell'operatore()

    Ciao volevo ringraziarti per il second post che hai postato (scusa il gioco di parole ), perché avevo appena iniziato a creare template ma mi compariva l'odiato messaggio di errore del linker. Grazie e alla prossima
Devi accedere o registrarti per scrivere nel forum
4 risposte