Entwicklung einer einfachen Matrizenklasse



  • Hallo,

    ich möchte gerne bei diesem Thema nochmal von 0 anfangen, um rauszufiltern, was bei meinen vorherigen Versionen nicht gestimmt hat. Anfangen möchte ich mit einem zweidimensionalen Feld aus ints.

    class Field2D
    {
    public:
    	Field2D(const std::size_t rows, const std::size_t columns)
    		: rows_(rows), columns_(columns)
    	{
    		elements_.resize(rows_ * columns_);
    	}
    
    	int element(const std::size_t row, const std::size_t column) const
    	{
    		if (row >= rows_ || column >= columns_)
    			throw std::exception();
    
    		const std::size_t idx = columns_ * row + column;
    		return elements_[idx];
    	}
    
    	void setElement(const std::size_t row, const std::size_t column, const int v)
    	{
    		if (row >= rows_ || column >= columns_)
    			throw std::exception();
    
    		const std::size_t idx = columns_ * row + column;
    		elements_[idx] = v;
    	}
    
    private:
    	std::size_t rows_ = 0;
    	std::size_t columns_ = 0;
    	std::vector<int> elements_;
    };
    

    Wenn das soweit ok ist, würde ich vorschlagen, den Klassennamen zu Matrix ändern, den int zu float machen und die Rechenoperationen hinzuzufügen.



    • Anstelle von std::exception könntest du besser https://en.cppreference.com/w/cpp/error/out_of_range werfen. Ggf. row und col einzeln testen für bessere Fehlermeldungen?
    • Du könntest eine private Funktion zur Berechnung des Index machen. Field2d::row_col_to_index oder so. Ich vergesse nämlich gerne, ob ich row-major oder col-major bin.
    • da die Funktionen element und setElement essentiell für eine Matrix sind, sollten diese einfachere Namen / über eine einfachere Syntax erreichbar sein. Du könntest z.B. den operator()(int, int) implementieren. Oder den operator[], dann müsstest du aber z.B. ein Zeilenobjekt returnen, welches einen weiteren operator[] haben müsste.


  • @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

      elements_.resize(rows_ * columns_);
    

    Warum nicht den ctor von vector nehmen?
    Warum sind die Parameter const?
    Warum ist der Elementtyp kein Templateparameter?

    @wob sagte in Entwicklung einer einfachen Matrizenklasse:

    Du könntest eine private Funktion zur Berechnung des Index machen.

    Das, und

    @wob sagte in Entwicklung einer einfachen Matrizenklasse:

    Oder den operator[], dann müsstest du aber z.B. ein Zeilenobjekt returnen, welches einen weiteren operator[] haben müsste.

    das.



  • Danke, damit werde ich ein wenig beschäftigt sein.

    @wob sagte in Entwicklung einer einfachen Matrizenklasse:

    • Du könntest eine private Funktion zur Berechnung des Index machen. Field2d::row_col_to_index oder so. Ich vergesse nämlich gerne, ob ich row-major oder col-major bin.

    Das verstehe ich glaub ich, nicht ganz. Du meinst, falls jemand eingibt (column, row), das dies automatisch zu (row, column) wird? Aber woher soll ich das wissen, ob jemand column meint und nicht row?

    @Swordfish sagte in Entwicklung einer einfachen Matrizenklasse:

    @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

      elements_.resize(rows_ * columns_);
    

    Warum nicht den ctor von vector nehmen?

    std::vector<int> elements_(rows * columns);?
    Und wie greife ich von dort mit element() zu?

    @Swordfish sagte in Entwicklung einer einfachen Matrizenklasse:

    Warum sind die Parameter const?

    Vor längerer Zeit hatte ich ein Stilfrage gestellt, ob es ok ist, alle Variablen, die sich nicht mehr ändern werden, auch die lokalsten constzu machen. Dies wurde bejaht und jemand schrieb, das er es auch so mache, nur bei den Parametern vergisst er es manchmal. Und ich dachte mir, warum nicht auch bei den Parametern, wenn sie sich nicht mehr ändern.

    Warum ist der Elementtyp kein Templateparameter?

    Weil wir noch bei 0 sind. Hatte ich mir aber auch überlegt, bin aber der Meinung, das kann gemacht werden, wenn die Rechenoperationen drin sind.



  • @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Das verstehe ich glaub ich, nicht ganz. Du meinst, falls jemand eingibt (column, row), das dies automatisch zu (row, column) wird?

    Nein.

    Ich meine, dass du bislang schon mehrfach const std::size_t idx = columns_ * row + column; geschrieben hast. Wenn du in alles weiteren Funktionen, die du noch so einbauen wirst, die Berechnung selbt machen willst, bitte. Wundere dich dann aber nicht, wenn du mal irgendwann const std::size_t idx = rows_ * column + row; schreibst (das ginge ja auch).

    Warum nicht den ctor von vector nehmen?

    std::vector<int> elements_(rows * columns);?
    Und wie greife ich von dort mit element() zu?

    Hä? Verstehe den Zusammenhang nicht.



  • @wob sagte in Entwicklung einer einfachen Matrizenklasse:

    Hä? Verstehe den Zusammenhang nicht.

    Die Frage war an Swordfish gerichtet. Wenn ich einen vector mit Konstruktor aufrufe, wird er ja dort erstellt. Wie bringe ich ihn dann in den Memberbereich? Ich wüßte jetzt nur

    std::vector<int> elements(rows * columns);
    elements_ = elements;
    

    Ansonsten danke nochmals. Hatte mir in der Zwischenzeit einige Gedanken zu den anderen Vorschlägen gemacht. Werde also etwas beschäftigt sein. Bis später oder so.



  • @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Die Frage war an Swordfish gerichtet.

    Initialisierungsliste

    #include <vector>
    
    template<typename T>
    class mat2d_t
    {
    public:
    	using value_type = T;
    	using reference = T&;
    	using const_reference = T const&;
    	using size_type = typename std::vector<T>::size_type;
    
    private:
    	size_type num_cols = 0;
    	std::vector<T> data;
    
    public:
    	mat2d_t() = default;
    	
    	mat2d_t(size_type rows, size_type cols, const_reference value = value_type{})
    	: num_cols { cols },
    	  data     (rows * cols, value)
    	{}
    };
    


  • Super, danke schön. Werde ich berücksichtigen.



  • wob meint:

    #include <vector>
    
    template<typename T>
    class mat2d_t
    {
    public:
    	using value_type = T;
    	using reference = T&;
    	using const_reference = T const&;
    	using size_type = typename std::vector<T>::size_type;
    
    private:
    	size_type num_cols = 0;
    	std::vector<T> data;
    
    public:
    	mat2d_t() = default;
    	
    	mat2d_t(size_type rows, size_type cols, const_reference value = value_type{})
    	: num_cols { cols },
    	  data     (rows * cols, value)
    	{}
    
    	// das da:
    private:
    	size_type to_linear_index(size_type row, size_type col) const
    	{
    		return num_cols * row + col;
    	}
    };
    


  • Kleiner Zwischenstand mit gestrichenen Segeln. Die Syntax f[x][y]mit Stichwort Proxy-Klasse kapiere ich einfach nicht. Mehr kann ich jetzt echt nicht mitteilen.
    Außer das da wahrscheinlich noch weitere Baustellen sind.
    Und, das ich mich für die Zeilen- und Spaltenanzahl auf std::size_t festlegen möchte. "einfache Matrizenklasse" soll wortwörtlich gelten.

    template<typename T>
    class Field2D
    {
    public:
    	using valType = T;
    	using valVec = std::vector<valType>;
    
    	Field2D() = default;
    	Field2D(const std::size_t rows, const std::size_t columns)
    		: columns_(columns), elements_(rows * columns) {}
    
    	std::size_t rows() const
    	{
    		return elements_.size() / columns_; //kann doch nicht sein???
    	}
    	std::size_t columns() const
    	{
    		return columns_;
    	}
    
    	valType element(const std::size_t row, const std::size_t column) const
    	{
    		const std::size_t idx = toLinearIndex(row, column);
    		return elements_[idx];
    	}
    
    	valType operator()(const std::size_t row, const std::size_t column) const
    	{
    		const std::size_t idx = toLinearIndex(row, column);
    		return elements_[idx];
    	}	
    
    	void setElement(const std::size_t row, const std::size_t column, const valType& v)
    	{
    		const std::size_t idx = toLinearIndex(row, column);
    		elements_[idx] = v;
    	}
    
    private:
    	std::size_t columns_ = 0;
    	valVec elements_;
    
    	std::size_t toLinearIndex(const std::size_t row, const std::size_t column) const
    	{
    		if (row >= rows())
    			throw std::out_of_range("toLinearIndex(): row: " + std::to_string(row));
    		if (column >= columns())
    			throw std::out_of_range("toLinearIndex(): column: " + std::to_string(column));
    
    		return columns() * row + column;
    	}
    };
    


  • @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    return elements_.size() / columns_; //kann doch nicht sein???
    

    Warum kann das nicht sein?



  • Weil ne extra Division für eine simple Rückgabe? Macht das nichts aus?



  • Wie oft brauchst Du das denn? Sonst musst Du es Dir eben merken.

    @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Die Syntax f[x][y] mit Stichwort Proxy-Klasse kapiere ich einfach nicht.

    So zB:

    #include <iostream>
    #include <vector>
    
    template<typename T>
    class mat2d_t
    {
    public:
    	using value_type = T;
    	using reference_type = T&;
    	using size_type = typename std::vector<T>::size_type;
    
    	class row_t
    	{
    		T* row_data;
    		size_type num_cols;  // fuer evtl range-check
    
    	public:
    		row_t(T *row_data, size_type num_cols)
    		: row_data{ row_data }, num_cols{ num_cols }
    		{}
    	
    		T& operator[](size_type col) { return row_data[col]; }
    	};
    
    private:
    	size_type num_cols = 0;
    	std::vector<T> data;
    
    public:
    	mat2d_t() = default;
    	
    	mat2d_t(size_type rows, size_type cols, value_type const &value = value_type{})
    	: num_cols { cols },
    	  data     ( rows * cols, value )
    	{}
    
    	row_t operator[](size_type row) {
    		return { &data[row * num_cols], num_cols };
    	}
    };
    
    int main()
    {
    	mat2d_t<int> mat2d(2, 5, 42);
    	for (std::size_t y{}, i{ 1 }; y < 2; ++y)
    		for (std::size_t x{}; x < 5; ++x, ++i)
    			mat2d[y][x] = i;
    
    	for (std::size_t y{}, i{ 1 }; y < 2; ++y, std::cout.put('\n'))
    		for (std::size_t x{}; x < 5; ++x, ++i, std::cout.put(' '))
    			std::cout << mat2d[y][x];
    }
    


  • Besten Dank mal wieder.

    Mache jetzt aber Feierabend , guts Nächtle... 😉





  • Jupp.



  • @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Und, das ich mich für die Zeilen- und Spaltenanzahl auf std::size_t festlegen möchte. "einfache Matrizenklasse" soll wortwörtlich gelten.

    Das Template dient doch aber dazu, dass die int, double oder sonstwas in deiner Matrix speichern kannst - irgendwas, mit dem man rechnen kann wie zum Beispiel big integers.

    Oder warum kommst du auf das size_t? Andererseits wird es vom vector ja schon automatisch vorgegeben.



  • Die Typ der Elemente der Matrix sollen natürlich möglichst beliebig sein, also alles mit was man rechnen kann.

    Ich meinte jetzt std::size_t columns_. Weil ich mir einbilde, dafür auch schon mal ein Template gesehen zu haben. Kann mich aber voll irren, alleine weil ich mir etwas anderes als size_tgar nicht vorstellen kann.



  • @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Weil ich mir einbilde, dafür auch schon mal ein Template gesehen zu haben.

    Du meinst einen Template-Typ-Parameter für den Typ der Größenangaben?



  • Richtig. Können aber auch Gespenster in meinem Kopf sein.


Anmelden zum Antworten