I've made a simple Matrix class to learn C++. It doesn't use Templates so it only works with int. I made it this way to really understand how to work with objects and memory. I'll probably move to the next level once I get feedback on this one.
I used these two blog posts as reference:
Description
- The numbers (
int) are stored in a 1D array of fixed size. We can reshape the matrix but the new shape can't modify the total size - It supports basic matrix and scalar operations (+,-,*)
- Some helpers functions are available like filling the matrix with one value, a range or random integers
One question I have is when I access a member of an instance, what is the difference between using this->m_member or just m_member (if there is one)?
Also, when an instance is passed by reference, I can access its members even the private ones. You can see this behavior on the implementation of the copy constructor and at many other places. Is it good practice? Or should I have a public getter for accessing the private member of a class, even when they are the same type?
CMatrix.hpp
#ifndef CMatrix_hpp
#define CMatrix_hpp
#include <iostream>
#include <random>
class CMatrix {
private:
int* m_ptValues;
int m_totalSize;
int m_rows;
int m_columns;
public:
CMatrix(int rows, int columns); // base ctor.
CMatrix(const CMatrix& rhs); // copy ctor.
CMatrix& operator=(const CMatrix& rhs); // assign. ctor.
~CMatrix(); // dtor.
int& operator()(int row, int column);
int& operator()(int row, int column) const;
CMatrix& operator+=(int scalar);
CMatrix operator+(int scalar);
CMatrix& operator-=(int scalar);
CMatrix operator-(int scalar);
CMatrix& operator*=(int scalar);
CMatrix operator*(int scalar);
CMatrix& operator*=(const CMatrix& rhs);
CMatrix operator*(const CMatrix& rhs);
CMatrix& operator+=(const CMatrix& rhs);
CMatrix operator+(const CMatrix& rhs);
void reshape(int newRows, int newColumns);
void show(); //used for dev. only
void range(int start, int defaultStep=1);
void fill(int value);
void randint(int lowerBound, int upperBound);
};
#endif /* CMatrix_hpp */
CMatrix.cpp
#include "CMatrix.hpp"
CMatrix::CMatrix(int rows, int columns) : m_rows(rows), m_columns(columns) {
m_totalSize = m_rows * m_columns;
m_ptValues = new int[m_totalSize]();
}
CMatrix::CMatrix(const CMatrix& rhs) : m_rows(rhs.m_rows), m_columns(rhs.m_columns) {
m_totalSize = rhs.m_totalSize;
m_ptValues = new int[rhs.m_totalSize]();
std::memcpy(m_ptValues, rhs.m_ptValues, rhs.m_totalSize * sizeof(int));
}
CMatrix& CMatrix::operator=(const CMatrix& rhs) {
if (&rhs == this) {
return *this;
}
if (m_totalSize == rhs.m_totalSize) {
std::memcpy(m_ptValues, rhs.m_ptValues, rhs.m_totalSize * sizeof(int));
} else {
delete[] m_ptValues;
m_ptValues = new int[rhs.m_totalSize]();
std::memcpy(m_ptValues, rhs.m_ptValues, rhs.m_totalSize * sizeof(int));
}
m_rows = rhs.m_rows;
m_columns = rhs.m_columns;
m_totalSize = rhs.m_totalSize;
return *this;
}
CMatrix::~CMatrix() {
if (m_ptValues) {
delete[] m_ptValues;
}
}
int& CMatrix::operator()(int row, int column) {
if (row >= m_rows || column >= m_columns) {
throw std::out_of_range("Index out of bounds");
}
return m_ptValues[row * m_columns + column];
}
int& CMatrix::operator()(int row, int column) const {
if (row >= m_rows || column >= m_columns) {
throw std::out_of_range("Index out of bounds");
}
return m_ptValues[row * m_columns + column];
}
CMatrix& CMatrix::operator+=(int scalar) {
for (auto i = 0; i < m_totalSize; i++) {
m_ptValues[i] += scalar;
}
return *this;
}
CMatrix CMatrix::operator+(int scalar) {
CMatrix result(m_rows, m_columns);
for (auto i = 0; i < m_totalSize; i++) {
result.m_ptValues[i] = m_ptValues[i] + scalar;
}
return result;
}
CMatrix& CMatrix::operator-=(int scalar) {
for (auto i = 0; i < m_totalSize; i++) {
m_ptValues[i] -= scalar;
}
return *this;
}
CMatrix CMatrix::operator-(int scalar) {
CMatrix result(m_rows, m_columns);
for (auto i = 0; i < m_totalSize; i++) {
result.m_ptValues[i] = m_ptValues[i] - scalar;
}
return result;
}
CMatrix& CMatrix::operator*=(int scalar) {
for (auto i = 0; i < m_totalSize; i++) {
m_ptValues[i] *= scalar;
}
return *this;
}
CMatrix CMatrix::operator*(int scalar) {
CMatrix result(m_rows, m_columns);
for (auto i = 0; i < m_totalSize; i++) {
result.m_ptValues[i] = m_ptValues[i] * scalar;
}
return result;
}
CMatrix& CMatrix::operator*=(const CMatrix& rhs) {
CMatrix result = (*this) * rhs;
(*this) = result;
return *this;
}
CMatrix CMatrix::operator*(const CMatrix& rhs) {
if (m_columns != rhs.m_rows) {
throw std::length_error("Matrices shapes mismatch");
}
CMatrix result(m_rows, rhs.m_columns);
int sum;
for (auto i = 0; i < m_rows; i++) {
for (auto j = 0; j < rhs.m_columns; j++) {
sum = 0;
for (auto k = 0; k < m_rows; k++) {
sum += this->operator()(i, k) * rhs(k,j);
}
result(i,j) = sum;
}
}
return result;
}
CMatrix& CMatrix::operator+=(const CMatrix& rhs) {
CMatrix result = (*this) + rhs;
(*this) = result;
return *this;
}
CMatrix CMatrix::operator+(const CMatrix& rhs) {
if (m_rows != rhs.m_rows || m_columns != rhs.m_columns) {
throw std::length_error("Matrices shapes mismatch");
}
CMatrix result(m_rows, m_columns);
for (auto i = 0; i < m_totalSize; i++) {
result.m_ptValues[i] = this->m_ptValues[i] + rhs.m_ptValues[i];
}
return result;
}
void CMatrix::reshape(int newRows, int newColumns) {
if (newRows * newColumns > m_totalSize) {
throw std::invalid_argument("Total size of new matrix must be unchanged");
}
m_rows = newRows;
m_columns = newColumns;
}
void CMatrix::show() {
std::string delimiter = "";
for (auto i = 0; i < m_rows; i++) {
delimiter = "";
for (auto j = 0; j < m_columns; j++) {
std::cout << delimiter << m_ptValues[i * m_columns + j];
delimiter = ",";
}
std::cout << std::endl;
}
}
void CMatrix::range(int start, int defaultStep) {
int counter = start;
for (auto i = 0; i < m_totalSize; i++) {
m_ptValues[i] = counter;
counter += defaultStep;
}
}
void CMatrix::fill(int value) {
for (auto i = 0; i < m_totalSize; i++) {
m_ptValues[i] = value;
}
}
void CMatrix::randint(int lowerBound, int upperBound) {
std::random_device rd;
std::mt19937 mt;
std::uniform_int_distribution<int> dist(lowerBound, std::nextafter(upperBound, __DBL_MAX__));
for (auto i = 0; i < m_totalSize; i++) {
m_ptValues[i] = dist(mt);
}
}
std::arrayandstd::vectoras the answer suggested. Would you say it is more common to do memory management or use a library that does it for us (like STL) in modern C++ ? \$\endgroup\$