Problem mit Klassentemplates



  • hallo ich habe folgendes Programm:

    //main.cpp
    #include <iostream>
    #include "Matrix.hpp"
    
    int main()
    {
    	Matrix <double>A(3, 4); //Neue 3*4-Matrix A erstellen
    
    	//Matrix A mit den in der Aufgabe vorgegebenen Werten füllen
    	for (int i = 0; i < 3; i++)
    	{
    		for (int j = 0; j < 4; j++)
    		{
    			A[i][j] = 13.5*i+j+1.4; 
    		}
    
    	}
    
    	Matrix<double> B = A; //Matrix B als Kopie von Matrix A erstellen
    	Matrix<double> Bt = B.Transpose(); //Matrix B transponieren
    	Matrix<double> AB = A*Bt; //Matrix A mit transponierter Matrix B multiplizieren
    	cout << AB<<endl; //Ergebnis auf dem Bildschirm ausgeben
    
    	system("cls");
    	return 0;
    }
    
    //matrix.hpp
    #ifndef _MATRIX_H_
    #define _MATRIX_H_
    
    #include <iostream>
    #include <cassert>
    using namespace std;
    
    template <typename T> class Zeile
    {
    	//Instanzvariablen
    	T *z;
    	int size;
    
    public:
    	//Konstruktor und Destruktor
    	Zeile(int s);
    	~Zeile();
    
    	//Liefert eine Referenz auf das i-te Element der Zeile, fängt Bereichsüberschreitungen ab
    	T& operator[](int i)
    	{
    		assert(i >= 0 && i < size);
    		return *(z+i);
    	}
    
    	//Liefert eine konstante Referenz auf das i-te Element der Zeile, fängt Bereichsüberschreitungen
    	const T& operator[](int i) const
    	{
    		assert(i >= 0 && i < size);
    		return *(z+i);
    	}
    };
    
    template <typename T> class Matrix
    {
    	//Instanzvariablen
    	Zeile<T> **mat;
    	int nrows, ncols;
    
    public:
    	//Konstruktoren und Destruktoren
    	Matrix<T>(int _nrows, int _ncols);
    	Matrix<T>(int z, int s, T wert);
    	Matrix<T>(const Matrix<T>&);
    	~Matrix<T>();
    
    	//Getter-Methoden
    	int GetRows() const
    	{
    		return nrows;
    	}
    
    	int GetCols() const
    	{
    		return ncols;
    	}
    
    	//Methoden-Prototypen?
    	Matrix<T> Transpose();
    	inline Zeile<T>& operator[](int i);
    	inline const Zeile<T>& operator[](int i) const;
    	Matrix<T>& operator=(const Matrix<T> &ma);	
    	friend ostream& operator<<(ostream &os, const Matrix<T> &m);
    	friend Matrix<T> operator+(const Matrix &ma<T>, const Matrix<T> &mb);
    	friend Matrix<T> operator*(const Matrix &ma<T>, const Matrix<T> &mb);
    };
    
    #endif
    
    //matrix.cpp
    #include "Matrix.hpp"
    using namespace std;
    
    template <typename T>Zeile<T>::Zeile(int s)
    {
    	z = new T[s];
    	size = s;
    
    	for (int i = 0; i < s; i++)
    	{
    		z[i] = 0;
    	}
    }
    
    template <typename T>Zeile::~Zeile()
    {
    	delete z;
    	z = 0;
    }
    
    template <typename T> Matrix<T>::Matrix(int _nrows, int _ncols)
    {
    
    	mat = new Zeile<T>*[_nrows];
    
    	for (int i = 0; i < _nrows; i++)
    	{
    		*(mat+i) = new Zeile<T>(_ncols);
    	}
    
    	nrows = _nrows;
    	ncols = _ncols;
    }
    
    template <typename T> Matrix<T>::Matrix(int _nrows, int _ncols, T wert)
    {
    	mat = new Zeile<T>*[_nrows];
    
    	for (int i = 0; i < _nrows; i++)
    	{
    		*(mat+i) = new Zeile<T>(_ncols);
    	}
    
    	for (int i = 0; i < _nrows; i++)
    	{
    		for (int j = 0; j < _ncols; j++)
    		{
    			(**(mat+i))[j] = wert;
    		}
    	}
    
    	nrows = _nrows;
    	ncols = _ncols;
    }
    
    template <typename T>Matrix<T>::Matrix(const Matrix &m)
    {
    	nrows = m.GetRows();
    	ncols = m.GetCols();
    
    	mat = new Zeile<T>*[nrows];
    
    	for (int i = 0; i < nrows; i++)
    	{
    		*(mat + i) = new Zeile<T>(ncols);
    	}
    
    	for (int i = 0; i < nrows; i++)
    	{
    		for (int j = 0; j < ncols; j++)
    		{
    			(**(mat + i))[j] = m[i][j];
    		}
    	}
    }
    
    template <typename T> Matrix<T>::~Matrix()
    {
    	for (int i = 0; i < nrows; i++)
    	{
    		delete *(mat + i);
    		*(mat + i) = 0;
    	}
    
    	delete mat;
    }
    
    template <typename T> Matrix<T> Matrix<T>::Transpose()
    {
    	Matrix<T> tmp(ncols, nrows);
    
    	for (int i = 0; i < ncols; i++)
    	{
    		for (int j = 0; j < nrows; j++)
    		{
    			tmp[i][j] = (**(mat + j))[i];
    		}
    	}
    
    	return tmp;
    }
    
    template <typename T> Zeile<T>& Matrix<T>::operator[](int i)
    {
    	return *(*(mat+i));
    }
    
    template <typename T> const Zeile<T>& Matrix<T>::operator[](int i) const
    {
    	return *(*(mat+i));
    }
    
    template <typename T> Matrix<T>& Matrix<T>::operator=(const Matrix<T> &ma)
    {
    	for (int i = 0; i < nrows; i++)
    	{
    		for (int j = 0; j < ncols; j++)
    		{
    			(**(mat + i))[j] = ma[i][j];
    		}
    	}
    
    	return *this;
    }
    
    template <typename T>Matrix<T> operator+(const Matrix<T> &ma, const Matrix<T> &mb)
    {
    	Matrix<T> tmp(ma);
    
    	for (int i = 0; i < ma.nrows; i++)
    	{
    		for (int j = 0; j < ma.ncols; j++)
    		{
    			tmp[i][j] = tmp[i][j] + mb[i][j];
    		}
    	}
    
    	return tmp;
    }
    
    template <typename T> Matrix<T> operator*(const Matrix<T> &ma, const Matrix<T> &mb)
    {
    	Matrix<T> mul(ma.nrows, mb.ncols);
    
    	for (int i = 0; i < mul.nrows; i++)
    	{
    		for (int j = 0; j < mul.ncols; j++)
    		{
    			for (int k = 0; k <ma.ncols ; k++)
    			{
    				mul[i][j] = mul[i][j] + ma[i][k] * mb[k][j];
    			}
    		}
    	}
    	return mul;
    }
    
    template <typename T> ostream& operator<<(ostream &os, const Matrix<T> &m)
    {
    	for (int i = 0; i < m.nrows; i++)
    	{
    		for (int j = 0; j < m.ncols; j++)
    		{
    			os << m[i][j]<<" ";
    		}
    		os << endl;
    	}
    
    	return os;
    }
    

    die aufgabe lautete, dieses programm zuerst für integer-variablen zu erstellen (funktionierte) und dann um klassentemplates zu erweitern. visual studio tut zumindest so, als würde es kompilieren, gibt dann aber nur die zahlen des ursprünglichen programms aus.

    habe ich da irgendetwas übersehen? was könnte da falsch sein?



  • visual studio tut zumindest so, als würde es kompilieren, gibt dann aber nur die zahlen des ursprünglichen programms aus.

    Mal mal nen Rebuild All.

    Desweiteren kannst du die Memberdefinitionen eines Klassentemplates nicht separat in eine Datei schreiben, es sei denn, du inkludierst die .cpp-Datei im Header direkt unter der Klassendefinition.



  • Wenn "Rebuild all" nichts ändert, hast du wohl das Projekt kaputtkonfiguriert. Das wird sich per Ferndiagnose wohl schlecht lösen lasse => neues Projekt mit den alten Dateien bauen.

    Grundsätzlich müssen Templates aber ausschließlich im Header implementiert werden. Deine Aufteilung in .h und .cpp wird damit also nicht (portabel 😉 funktionieren.

    [*] möglicherweise gibt es da in VS einer Microsoft Erweiterung, mit der es dann doch übersetzt.


  • Mod

    Zwar sicher nicht Ursache des geschilderten Problems aber der Zuweisungsoperator von Matrix ist fehlerhaft (was nicht weiter auffällt, da er nicht benutzt wird).
    Ansonsten könnte einiges zu diesem Code gesagt werden; bevor ich aber einen Roman schreibe, frage ich erst mal nach, ob eine Diskussion erwünscht ist.



  • camper schrieb:

    Zwar sicher nicht Ursache des geschilderten Problems aber der Zuweisungsoperator von Matrix ist fehlerhaft (was nicht weiter auffällt, da er nicht benutzt wird).
    Ansonsten könnte einiges zu diesem Code gesagt werden; bevor ich aber einen Roman schreibe, frage ich erst mal nach, ob eine Diskussion erwünscht ist.

    was meinst du mit zuweisungsoperator von matrix?
    die methodendeklarationen waren so durch die aufgabe vorgegeben, also wir sollten nur die rümpfe implementieren.

    wenn ich ein neues projekt erstelle und alles in einer datei unterbringe, bekomme ich einen linkerfehler!?

    Severity Code Description Project File Line Suppression State
    Error LNK2019 unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Matrix<double> const &)" (??6@YAAAV?basic_ostream@DU?basic\_ostream@DU?char_traits@D@std@@@std@@AAV01@ABV?$Matrix@N@@@Z) referenced in function _main ConsoleApplication2 c:\Users\r. peglow\documents\visual studio 2015\Projects\ConsoleApplication2\ConsoleApplication2\Source.obj 1

    also um mal eine direkte frage zu stellen: habe ich das prinzip der anwendung von klassentemplates verstanden, oder ist das alles komplett falsch?

    und natürlich ist eine diskussion erwünscht, außer sie steht im widerspruch zu obiger aussage, dass gewisse teile durch die aufgabe vorgegeben waren. 😉


  • Mod

    Ralf4711 schrieb:

    camper schrieb:

    Zwar sicher nicht Ursache des geschilderten Problems aber der Zuweisungsoperator von Matrix ist fehlerhaft (was nicht weiter auffällt, da er nicht benutzt wird).
    Ansonsten könnte einiges zu diesem Code gesagt werden; bevor ich aber einen Roman schreibe, frage ich erst mal nach, ob eine Diskussion erwünscht ist.

    was meinst du mit zuweisungsoperator von matrix?
    die methodendeklarationen waren so durch die aufgabe vorgegeben, also wir sollten nur die rümpfe implementieren.

    An der Deklaration ist nichts auszusetzen.
    Was passiert, wenn in main noch ein

    Bt = B;
    

    eingefügt wird?

    Ralf4711 schrieb:

    wenn ich ein neues projekt erstelle und alles in einer datei unterbringe, bekomme ich einen linkerfehler!?

    Severity Code Description Project File Line Suppression State
    Error LNK2019 unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Matrix<double> const &)" (??6@YAAAV?basic_ostream@DU?basic\_ostream@DU?char_traits@D@std@@@std@@AAV01@ABV?$Matrix@N@@@Z) referenced in function _main ConsoleApplication2 c:\Users\r. peglow\documents\visual studio 2015\Projects\ConsoleApplication2\ConsoleApplication2\Source.obj 1

    Der Fehler dürfte auch für den *-Operator auftreten (ebenso + wenn er verwendet würde).
    Der Grund hierfür liegt darin, dass die friend-Deklarationen in der Definition des Klassentemplates nicht zu den späteren Definitionen passen.
    Ich beschränke mich mal auf den *-Operator, für die anderen gilt es analog:

    template <typename T> class Matrix
    {
    ...
        friend Matrix<T> operator*(const Matrix<T> &ma, const Matrix<T> &mb);
    };
    
    template <typename T> Matrix<T> operator*(const Matrix<T> &ma, const Matrix<T> &mb)
    {
    ...
    

    Die friend-Deklaration in der Definition von Matrix deklariert gewöhnliche Funktionen als friend. Die spätere Definition definiert ein Funktionstemplate.
    Die Tatsache, dass sie gleichen Funktionssignaturen haben, führt nicht dazu dass

    Matrix<int> operator*(const Matrix<int>&, const Matrix<int>&)
    

    und

    Matrix<int> operator*<int>(const Matrix<int>&, const Matrix<int>&)
    

    die gleichen Funktionen wären.
    Was passiert also wenn der Compiler den Ausdruck ABt sieht? Er findet zwei Funktionen, die passen könnten, die gewöhnliche Funktion
    Matrix<double> operator
    (const Matrix<double>&, const Matrix<double>&)
    und das Template
    Matrix<T> operator*(const Matrix<T> &ma, const Matrix<T> &mb)
    mit der Spezialisierung T=double. Beide passen gleich gut und die Überladungsregeln besagen, dass wenn zwei Kandidaten gleich gut sind, und einer eine gewöhnliche Funktion, der andere eine Templatespezialisierung ist, die gewöhnliche Funktion ausgewählt wird. Dummerweise existiert für diese gewöhnliche Funktion keine Definition und das bringt den Linker in Schwierigkeiten...

    Versuchen wir, die Definition zu korrigieren, dass sieht zur friend-Deklaration passt, wird es sofort schwierig.
    Natürlich könnte man eine Definition für T=double geben

    Matrix<double> operator*(const Matrix<double> &ma, const Matrix<double> &mb)
    {
    ...
    }
    

    und dann noch für int

    Matrix<int> operator*(const Matrix<int> &ma, const Matrix<int> &mb)
    

    usw. aber das ist offensichtlich keine allgemeine Lösung, die für jedes sinnvolle Argument T passt. Tatsächlich gibt es keine Möglichkeit, die in der friend-Deklaration deklarierten Funktion für alle T ausserhalb der Matrix-Definition zu definieren.
    Sollen die friends also unverändert erhalten bleiben, kann die Definition nur inline innerhalb der Matrix-Definition erfolgen:

    template <typename T> class Matrix
    {
    ...
        friend Matrix<T> operator*(const Matrix<T> &ma, const Matrix<T> &mb)
        {
        ...
        }
    };
    

    Alternativ können wir beim Funktionstemplate verbleiben und die friend-Deklaration anpassen:

    template <typename T> class Matrix
    {
    ...
        // macht das Funktionstemplate zum friend, alle Spezialisierungen des friend-Templates werden zu
        // friends jeder Matrix-Spezialisierung (also mehr friend als nötig, dafür geringerer Schreibaufwand)
        template <typename U> friend Matrix<U> operator*(const Matrix<U> &ma, const Matrix<U> &mb);
    };
    

    oder

    template <typename T> class Matrix;
    template <typename T> friend Matrix<T> operator*(const Matrix<T> &ma, const Matrix<T> &mb);
    template <typename T> class Matrix
    {
    ...
        friend Matrix<T> operator*<T>(const Matrix<T> &ma, const Matrix<T> &mb);
    };
    

Anmelden zum Antworten