• 2D array / Matrix in class
    17 replies, posted
I'm trying to make a Matrix solver... I'm making a Matrix class to be able to manipulate is as an object. I want my matrix to be a 2D array of doubles. I'm having a problem creating the array. I want the array to have a class wide scope and I want to set its size in the constructor (I have a colums and a rows arguments) If there's a more efficient way to do it please tell me... From what I know it seems like the good way to do it but I might be wrong. I'm currently learning C++, I know quite a lot of stuff but some syntax and more complex things are still a little confusing to me. I know this solution has something to do with pointers Please help me.. My header [cpp] /* * Matrix.h * * Created on: 17 Nov 2009 * Author: boris */ #ifndef MATRIX_H_ #define MATRIX_H_ class Matrix { private: //declaring matrix here (2d Array) public: Matrix(int sizeX,int sizeY); /* Other methods */ virtual ~Matrix(); }; #endif /* MATRIX_H_ */ [/cpp] My cpp file [cpp] /* * Matrix.cpp * * Created on: 17 Nov 2009 * Author: boris */ #include "Matrix.h" Matrix::Matrix(int sizeX, int sizeY) { //the size of the array should be set here // Set the Matrix' size with the sizeX and sizeY arguments // TODO Auto-generated constructor stub } /* Methods here */ Matrix::~Matrix() { // TODO Auto-generated destructor stub } [/cpp]
Indefinite-sized matrices? Possible to implement, but any way you do it, it's gonna be hacky and slow, not to mention all the special functions you do with matrices (like inverse) which aren't always defined for nxm sized matrices. You're better off using templates, as allocating the space for them is way less hacky and the data would be stack-allocated. But you're still faced with matrix math problems with indefinite dimensions. [cpp] /* file matrix.h */ //n is columns and m is rows template <unsigned int N, unsigned int M> class Matrix { public: double values[N][M]; //matrix data arranged into a 2D array (column-major ordering in memory!!) const unsigned int columns = N; const unsigned int rows = M; Matrix(); ~Matrix(); }; [/cpp] However, a lot of the math would be a lot easier if you made sure that you could only have [i]square[/i] matrices.
I'll try that but I'm not sure how templates work. I have to check it out. How would I create an instance of my class using the template. It didn't work... I got his error [code]previous declaration of &#8216;template<unsigned int R, unsigned int C> class Matrix&#8217;[/code] I would still like to know how to create a 2d array and allocate memory for it after... I know it works for Char arrays [cpp] char * myString; myString = new char[256]; [/cpp] [editline]10:55PM[/editline] Bump. I edited my post...
To instance with a template, you have the template declaration in the header file only, right? Well in your C++ source, you would instance a matrix with the template parameters that define its size: [cpp] #include "matrix.h" int main(void) { //Make a 3 x 4 matrix Matrix<3, 4> mat34; .... //Make a 1-column matrix (a vector) Matrix<1, 4> vector; .... } [/cpp] The trouble with dynamic allocation (what you are trying to do with the 'new' operator) is that it doesn't really support 2D arrays. You can only have 1 undetermined dimension. (I should perhaps clarify that you can make N dimensional arrays with 'new,' but N-1 dimensions are of pre-determined size, which doesn't help you. That's why I'm telling you to use templates.) For example, while you could do this: [cpp] char* foo ( int x ) { return new char[x]; } [/cpp] and even this: [cpp] char* bar ( int x ) { return new char[10][x]; //2D array with a stride of 10 } [/cpp] But you can't do this: [cpp] char* baz (int x, int y) { return new char [x][y]; } [/cpp] [editline]01:06AM[/editline] If you're still [i]really[/i] dead-set on dynamic allocation, I suppose you could do a 1D <-> 2D mapping scheme, where you lay all the values out into a 1-dimensional array. (Computer memory is one-dimensional after all). You allocate N x M doubles, and you access the element (i, j) [i an j indicate which column and row to pick] you return the (M*j + i)'th element of the array. I think I may have transposed the column-major ordering with a row-major ordering. It's a pain to keep this crap straight. You see why I suggest learning templates? It will help you learn C++. (Which you yourself claim is better because of it's relative newness to C)
Here's what I did. (errors are in comments) [cpp] /* * Matrix.h * * Created on: 17 Nov 2009 * Author: boris */ #ifndef MATRIX_H_ #define MATRIX_H_ template <unsigned int R, unsigned int C> class Matrix { // <- Error: previous declaration of &#8216;template<unsigned int R, unsigned int C> class Matrix&#8217; private: const unsigned int rows = R; // <- Error: Multiple markers at this line // - ISO C++ forbids initialization of member &#8216;rows&#8217; // - making &#8216;rows&#8217; static const unsigned int cols = C; // <- Error: Multiple markers at this line // - ISO C++ forbids initialization of member &#8216;cols&#8217; // - making &#8216;cols&#8217; static double matrix[rows][cols]; //unsigned int rows = R; //unsigned int cols = C; //declaring matrix (2d Array) public: Matrix(/*unsigned int sizeX,unsigned int sizeY*/); int swapRows(int row1,int row2); int addRows(int row1, int row2); int addMulRow(int row1, double mul, int row2); int fill(double num); int draw(); double retunCell(int X, int Y); virtual ~Matrix(); }; #endif /* MATRIX_H_ */ [/cpp] I think the error is happening because I have a separate cpp file with my code... For Example [cpp] int Matrix::fill(double num){ //some code } [/cpp] In here the class is called but no information is sent to the template (I'm doing guess work right now) I'm getting this error for that code (and every other method/function) [code] &#8216;template<unsigned int R, unsigned int C> class Matrix&#8217; used without template parameters [/code] How can I fix this?
You could just do [cpp]template<unsigned int rows, unsigned int cols>[/cpp] As for the method, you need to define all methods in the header-file for template-classes and functions, because they are created in compile-time. And you also need to define them like this: [cpp]template<unsigned int rows, unsigned int cols> int Matrix<rows, cols>::fill(double num) { //some code }[/cpp] You could spare some typing by #defining something short(er) to template<unsigned int rows, unsigned int cols>.
The "previous declaration of ‘template<unsigned int R, unsigned int C> class Matrix’" isn't actually an error, it's an informational message from the compiler related to another error message, probably the "‘template<unsigned int R, unsigned int C> class Matrix’ used without template parameters" one. The reason is that you're defining member functions for Matrix as if it were a non-template class, and the compiler is telling you "you can't do that, dummy, see the previous declaration here where you said it was a template?" For example, your fill function should be defined as: [cpp] template <unsigned int R, unsigned int C> int Matrix<R, C>::fill(double num) { // ... } [/cpp] Think of it this way: Matrix is not a class, it's a template used for generating classes. Matrix<1, 2> is a class. Matrix<3, 4> is another class, and so on. You [i]could[/i] actually write separate implementations of fill() for all those classes -- e.g. "int Matrix<1, 2>::fill(double num)" -- but instead you're using a template to generate fill() functions for each Matrix class (which is what's typically done). The reason you get errors about the initialization of the R and C variables is because non-static (that is, per-instance) variables can't be initialized directly like that; you have to use a constructor. It would work if you made them static, but there's no need for these variables because you already have R and C in the form of template parameters. Your array can be defined as: [cpp] double matrix[R][C]; [/cpp]
I fixed some errors but I go new ones... My class builds now. In my main function I'm getting this weird error [code] undefined reference to `Matrix<2, 2>::Matrix()' [/code] On this piece of code I don't seem to be able to create an instance of my class [cpp] #include "Matrix.h" int main(){ Matrix<2, 2> myMatrix; //error here myMatrix.fill(2); //another error Matrix<2, 2>::fill() instead of Matrix<2, 2>::Matrix() } [/cpp] Also, please tell me if I got templates right. Templates are functions used to create different versions of a function or class They work like a template for something else. You specify some variables and you get a different class or function.
[QUOTE=Boris-B;18458894]In my main function I'm getting this weird error [code] undefined reference to `Matrix<2, 2>::Matrix()' [/code] [/QUOTE] That's the constructor, and "undefined reference" means that the compiler thought that this function would exist (because you declared a constructor in the class), but couldn't actually find the code for it when it went looking. Did you forget to write the constructor? [QUOTE=Boris-B;18458894] [cpp] myMatrix.fill(2); //another error Matrix<2, 2>::fill() instead of Matrix<2, 2>::Matrix() [/cpp] [/QUOTE] If that's an undefined reference too, it means the compiler (well, actually, the linker) couldn't find the code for fill() either. I assume you didn't forget to write that one, since you tried writing it already and I told you how to fix it in my previous post. If you wrote code for fill() and you're still getting this error, please post the contents of your Matrix.h file. [editline]11:52PM[/editline] BTW, why are you declaring a virtual destructor for your class? You don't need to write a destructor for this class, since it has no dynamically-allocated memory or other resources that need to be cleaned up, and destructors generally aren't made virtual unless the class is intended to be used polymorphically (i.e. it has other virtual functions).
This fonction create a matrix of variable size in C++, you just need to adapt it for your class. [cpp] int** createArray(int x, int y){ int** array = new(int*[x]); //We create a first array of integers pointer with the number of row in our matrix for(int i = 0; i<x; i++) array[i] = new(int[y]); //Then we fill each cell of the array, with an array of integer with the number of column in our matrix return array; } [/cpp] Now when your done with your Matrix, you need to free it here is the function : [cpp] void killArray(int** array, int x){ for(int i = 0; i<x; i++) delete(array[i]); delete array; } [/cpp] And to use it just do this : [cpp] int** array = createArray(5,5); array[1][1] = 10; killArray(array,5); [/cpp] There should be a better way to do this. But it works, and there isn't any leak.
[QUOTE=Cyril;18466937] [cpp] void killArray(int** array, int x){ for(int i = 0; i<x; i++) delete(array[i]); delete array; } [/cpp] [/QUOTE] [list] [*]You're freeing arrays, so you should be using delete[], not delete. [*]This code belongs in a destructor, not a freestanding function that has to be called manually. [*]You don't need to use pointers-to-pointers to get the effect of a 2D array. You can allocate a single 1D array of (rows*cols) length, and use ((row * cols) + col) to look up elements within it. [*]Rather than allocating that 1D array yourself with new, you can use a std::vector, so that you don't need any cleanup code. [*]Dynamically allocating the array is unnecessary when the desired size is known at compile time. What we've been discussing above is a different approach that doesn't involve a "new" or "delete" anywhere. [/list]
I just realized that the template is not going to work because I want to define the matrix' size at runtime. It looks like I'll have to dynamically allocate it... Can't I just make an array of pointers that point to my rows? I still have a problem with pointers and this is confusing to be (syntax wise) I know how pointers work but I don't know how to write the proper syntax for it.
I don't have time at the moment for a lengthy reply, but as I said to Cyril in my previous post, you only need one pointer, not an array of pointers. Imagine that your matrix size is 10x10. You can allocate a single array with 100 elements. Row 0 is stored at array[0] through array[9], row 1 is stored at array[10] through array[19], and so on. To find the array index for a particular row and column, multiply the row number by the matrix's width to get the starting index for that row (e.g. array[10] for row 1, array[30] for row 3, etc.) and then add the column number. If you want your matrix to be stored in column-major order rather than row-major, swap the words "row" and "column", and "width" and "height", in what I just wrote above.
I did some research and I found 2 solutions. This first one is tho use a vector of vectors and the other one is to use a pointer to pointer. Vector of Vectors looks easier because I won't need a destructor and It will manage memory in a better way... [editline]10:26AM[/editline] The vectors gave me troubles... I decided to try Cyril's solution and it worked... I'm interested in your solution Wyzard. How would you achieve such a thing
[QUOTE=Wyzard;18467923][list] [*]You're freeing arrays, so you should be using delete[], not delete. [/list] [/QUOTE] Didn't know about that, thanks. [QUOTE=Wyzard;18467923] [list] [*]This code belongs in a destructor, not a freestanding function that has to be called manually. [/list] [/QUOTE] As I said, he needs to port it into his class, so it was obvious that he would put this in the destructor and not in a function. [QUOTE=Wyzard;18467923] [list] [*]You don't need to use pointers-to-pointers to get the effect of a 2D array. You can allocate a single 1D array of (rows*cols) length, and use ((row * cols) + col) to look up elements within it. [/list] [/QUOTE] I thought it would by easier to implement math functions by using 2D array. So you don't need to convert the coordinates. [QUOTE=Wyzard;18467923] [list] [*]Rather than allocating that 1D array yourself with new, you can use a std::vector, so that you don't need any cleanup code. [/list] [/QUOTE] Ok, didn't thought to use it. [QUOTE=Wyzard;18467923] [list] [*]Dynamically allocating the array is unnecessary when the desired size is known at compile time. What we've been discussing above is a different approach that doesn't involve a "new" or "delete" anywhere. [/list][/QUOTE] I thought that the OP wanted a way to dynamically allocate the matrix so he doesn't have to recompile his project for each Matrix.
Wyzard's solution (which is similar to what I proposed with the 2D<->1D mapping) Here is just a simple accessor class that you could have in a matrix definition. [cpp] /* matrix.h */ ... matrix definition ... class Matrix_Data { public: Matrix_Data(int rows, int columns); ~Matrix_Data(); //Accessor function //Get the number in the matrix identified by the specified row and column double& getElement(int row, int column) const; double& setElement(int row, int column, const double& val); double* values; //Pointer to an array on the heap int R, C; //Rows and columns width }; [/cpp] And the source: [cpp] #include "matrix.h" Matrix_Data::Matrix_Data(int rows, int columns) { R = rows; C = columns; values = new double[R*C]; //Allocate the space. Should be set to all 0's } Matrix_Data::~Matrix_Data() { delete[] values; //Free from memory } //Rows and columns start at (0,0) and are defined through (R-1,C-1) double& Matrix_Data::getElement(int row, int column) const { //Column-major ordering return values[column * R + row]; } double& Matrix_Data::setElement(int row, int column, const double& val) { return values[column * R + row] = val; } [/cpp] This is basically just a wrapper for interpretting some 1D data as a 2D array. Don't use it as your raw matrix class. Also, this quickie thing has no sanity checks on arguments. You should always check that, lest you get an out-of-bounds access error on the array.
I think that I'm going to stick with the pointer-to-pointer solution... It's less trouble to navigate through the matrix. I would like tot hank you all guys for helping me out. I learned a great deal. I think that my class didn't work with a template because it didn't exactly act like a class and couldn't either recognize my methods because of the template before it or it didn't know where to find the corresponding cpp file... Thank you guys... If I manage to mess something up again I'll post here again... Hopefully I don't... [editline]02:25PM[/editline] Just read Cath's post... thank you man... i'll make sure not to allow the matrix to go out of bounds The way of doing wyzard's solution hit me in the face while I was on the bus. Thanks anyways.
Here's how I might implement this class: [cpp] #include <algorithm> #include <vector> template <typename T> class Matrix2D { public: typedef T value_type; // Standard practice for container classes, used by generic algorithms Matrix2D(unsigned int width, unsigned int height); // Used to support matrix[x][y] syntax class Column { public: T &operator[](unsigned int y); private: friend class Matrix2D; Column(std::vector<T> &values, unsigned int top_index); std::vector<T> &values; unsigned int top_index; // values[top_index] is the element at the top of this column }; // Same as Column, but for const matrices, to prevent the value from being modified // (Note that operator[] returns a const reference) class ColumnConst { public: T const &operator[](unsigned int y); private: friend class Matrix2D; ColumnConst(std::vector<T> const &values, unsigned int top_index); std::vector<T> const &values; unsigned int top_index; // values[top_index] is the element at the top of this column }; Column operator[](unsigned int x); ColumnConst operator[](unsigned int x) const; void fill(T const &value); // The OP had this function so I'll provide it too private: unsigned int width, height; std::vector<T> values; // Linear storage in column-major order }; // Matrix2D constructor template <typename T> Matrix2D<T>::Matrix2D(unsigned int width, unsigned int height): width(width), height(height), values(width * height) { } // Column constructor template <typename T> inline Matrix2D<T>::Column::Column(std::vector<T> &values, unsigned int top_index): values(values), top_index(top_index) { } // ColumnConst constructor template <typename T> inline Matrix2D<T>::ColumnConst::ColumnConst(std::vector<T> const &values, unsigned int top_index): values(values), top_index(top_index) { } // matrix[x] gets a column, which can then be indexed with [y] template <typename T> inline typename Matrix2D<T>::Column Matrix2D<T>::operator[](unsigned int x) { return Column(values, x * height); } // matrix[x] gets a column, which can then be indexed with [y] // (for const matrices) template <typename T> inline typename Matrix2D<T>::ColumnConst Matrix2D<T>::operator[](unsigned int x) const { return ColumnConst(values, x * height); } // get a value from a column obtained with matrix[x] template <typename T> inline T & Matrix2D<T>::Column::operator[](unsigned int y) { return values[top_index + y]; } // get a value from a column obtained with matrix[x] // (for const matrices) template <typename T> inline T const & Matrix2D<T>::ColumnConst::operator[](unsigned int y) { return values[top_index + y]; } // set all matrix elements to the same value template <typename T> void Matrix2D<T>::fill(T const &value) { std::fill(values.begin(), values.end(), value); } [/cpp] The purpose of the Column and ConstColumn classes is to let you access elements by writing matrix[x][y], just like you would with a plain 2D array. When you write matrix[x] you get a column object, and then you can do [y] on that. These functions are inline so that the compiler can see "through" both operator[] calls and the construction of the temporary column object, so it should be able to optimize it into just calculating the array index and accessing the vector directly. Since the values are stored in a std::vector (rather than allocating an array manually with new), there's no need to implement a destructor, copy constructor, or assignment operator, because the default ones are sufficient. The vector handles its own copying and destruction.
Sorry, you need to Log In to post a reply to this thread.