# Custom matrix object in C++

I have been getting back into C++ today, after a few years of Python and R. I am completely rusty, but decided to create a matrix object to re-familiarise. In reality I wouldn't use this class, since I know Boost has matrix objects, but it's good practice!

It can be compiled with g++ -std=c++11 matrix.cpp -o m.

Any thoughts or comments are most welcome.

#include <iostream>
#include <vector>

class matrix {
// Class: matrix
//
// Definition:
// creates a 2d - matrix object as a vector of vectors and
// populates with (int) zeros.
//
// Methods: get_value, assign_value, get_row.

// create vector of vectors
std::vector<std::vector<int> > m;
public:
// constructor for class, matrix dimension (rows=X, cols=Y).
matrix( int X, int Y) {
m.resize(X, std::vector<int>(Y, 0));
}

class row {
//class for matrix row object. Pass in a
// vector and overload [].

std::vector<int> _row;
public:
// constructor
row(std::vector<int> r) : _row(r) {
}

// overload [] to return y element.
// note .at() does a range check and will throw an error
// if out of range
int operator[]( int y) {
return _row.at(y);
}
};

// overload [] to return x element
row operator[]( int x) {
return row(m.at(x));
}

int get_value ( int x, int y ) {
//  Function: get_value
//  Definition: returns value v of element
//  xy in matrix M.
return m[x][y];
}

void assign_value ( int x, int y, int v ) {
//  Function: assign_value
//  Definition: Assigns value v to element
//  xy in matrix.
m[x][y] = v;
}

std::vector<int> get_row(int y, int X){
// Function get_row
// Definition: returns a vector object with row
// of x-values of length X at y.
std::vector<int> ROW;
for ( int i=y; i<=y;i++){
for (int j=0; j<=X-1;j++){
ROW.push_back(m[i][j]);
}
}
return ROW;
}

};

int main(){

// specify matrix dimensions
int N = 10; // number of rows
int M = 10; // number of cols

// create a matrix object
matrix mm(N,M);

// print elements
int i, j;
for (i=0; i<=N-1;i++){
for (j=0;j<=M-1; j++){
std::cout << mm[i][j];
}
std::cout << std::endl;
}

// grab a value and print it to console
std::cout << mm.get_value(0,0) << std::endl;

// assign a new value (v = 1) to element (0,0)
mm.assign_value(0,0,1);

// re-print the updated matrix
for (i=0; i<=N-1;i++){
for (j=0;j<=M-1; j++){
std::cout << mm[i][j];
}
std::cout << std::endl;
}

// get_row() test
std::vector<int> R = mm.get_row(0, M);
for( int i: R){
std::cout << i << ' ';
}
}

• As well as Boost, it may be instructive to look at OpenCV for inspiration. Although it's only a thin C++ wrapper over a C implementation, it does lean heavily on matrix operations, and could provide additional insight into which features are valuable to users. – Toby Speight Sep 18 '18 at 10:39

That's some nicely presented code. I found it very easy to read and understand.

A vector of rows isn't the best structure for a matrix. The reason is that each vector has its storage elsewhere, so you lose locality of access. A better structure is a flat array (or vector) of elements, and a knowledge of the stride from one row to the next. (we can make the stride be the same as the row length, for simplicity; separate members for width and stride can be useful in more advanced scenarios).

    std::vector<int> m;
std::size_t width;

public:
# Constraint: x * y must not overflow size_t
matrix(std::size_t x, std::size_t y)
: m(x*y, 0),
width{x}
{
}


I've made the dimensions be size_t, as that's the natural type for a size or count in C++.

Now, when we need to index into the array, we need to multiply the y value by width and add x:

int get_value(std::size_t x, std::size_t y)
{
return m[x + y*width];
}


We can improve on this, by returning a reference to the value. Instead of having a "get" and "set" method, we have a single method (for now), and we can give it a more convenient name:

int& operator()(std::size_t x, std::size_t y)
{
return m[x + y*width];
}


This means that instead of having to write

mm.assign_value(0,0,1);


we can instead use the more intuitive

mm(0,0) = 1;


Now it's time admit to a slight lie above. We actually need two methods, because if we have a const matrix, we should be allowed to read, but not write, its elements. So we also need:

const int& operator()(std::size_t x, std::size_t y) const
{
return m[x + y*width];
}


For printing, it's helpful to provide an operator<<(). Mine would look like this:

friend auto& operator<<(std::ostream& os, const matrix& m)
{
for (std::size_t row = 0;  row < m.height;  ++row) {
for (std::size_t col = 0;  col < m.width;  ++col) {
os << m.m[col + row*m.width] << ' ';
}
os << '\n';
}
return os;
}


I added a height member to make this easier.

With these changes, see how much easier it is to use:

#include <iostream>

int main()
{
// create a matrix object
matrix mm(4,6);

// print elements
std::cout << mm;

// grab a value and print it to console
std::cout << mm(0,0) << std::endl;

// assign a new value (v = 1) to element (0,0)
mm(0,0) = 1;

// re-print the updated matrix
std::cout << mm;
}


Here's the full version of matrix after my edits:

#include <ostream>
#include <vector>

class matrix {
std::vector<int> m;
std::size_t width;
std::size_t height;

public:
matrix(std::size_t x, std::size_t y)
: m(x*y, 0),
width{x},
height{y}
{
}

int& operator()(std::size_t x, std::size_t y)
{
return m[x + y*width];
}

const int& operator()(std::size_t x, std::size_t y) const
{
return m[x + y*width];
}

friend auto& operator<<(std::ostream& os, const matrix& m)
{
for (std::size_t row = 0;  row < m.height;  ++row) {
for (std::size_t col = 0;  col < m.width;  ++col) {
os << m.m[col + row*m.width] << ' ';
}
os << '\n';
}
return os;
}
};


## Further exercises

• If you actually want a public get_row() (and get_column()), these will need new implementations, perhaps copying values.
• Think about providing a get_subarray(x, y, width, height) to give a view of part of the matrix - you'll need new members for offset and stride. See how we can now more easily implement get_row() and get_column() using this new method.
• Make the matrix a template, so we can have elements of whatever type we choose, rather than only int.