How to improve "=" operator overload for matrices? - c++

I have overloaded assignment operator for the class with a 2D array, but in order to do memory management and resizing correct I have to delete previous matrix first, then construct a new one, and only then I can start assigning.
Matrix& Matrix::operator = (const Matrix& m1){
for (int i = 0; i < m_rows; ++i)
delete[] m_matrix[i];
delete[] m_matrix;
m_matrix = new double*[m1.rows()];
for (int i = 0; i < m1.rows(); ++i)
m_matrix[i] = new double[m1.cols()]();
for (int k = 0; k < m1.rows(); ++k)
for (int j = 0; j < m1.cols(); ++j)
m_matrix[k][j] = m1.m_matrix[k][j];
m_rows = m1.rows();
m_cols = m1.cols();
return *this;
}
In fact, this part is destructor of my class:
for (int i = 0; i < m_rows; ++i)
delete[] m_matrix[i];
delete[] m_matrix;
And this part is similar to a constructor:
m_matrix = new double*[m1.rows()];
for (int i = 0; i < m_rows; ++i)
m_matrix[i] = new double[m1.cols()]();
What annoys me is that I have to copy constructors' and destructors' code in the assignment function (and some other functions too!) to make it work properly. Is there a better way to write it?

The ideal improvement would be Matrix& Matrix::operator=(const Matrix&) = default;.
If you switch to using std::vector for matrix storage you won't need to implement the copy/move constructors/assignments and destructor at all.
If what you are doing is a programming exercise, create your own dynamic array and use that in the implementation of your matrix.
I cannot recommend enough watching Better Code: Runtime Polymorphism by Sean Parent, he makes an effective demonstration of why you should strive to write classes that do not require non-default implementations of copy/move constructors/assignments and destructor.
Example:
template<class T>
class Matrix
{
std::vector<T> storage_;
unsigned cols_ = 0;
public:
Matrix(unsigned rows, unsigned cols)
: storage_(rows * cols)
, cols_(cols)
{}
// Because of the user-defined constructor above
// the default constructor must be provided.
// The default implementation is sufficient.
Matrix() = default;
unsigned columns() const { return cols_; }
unsigned rows() const { return storage_.size() / cols_; }
// Using operator() for indexing because [] can only take one argument.
T& operator()(unsigned row, unsigned col) { return storage_[row * cols_ + col]; }
T const& operator()(unsigned row, unsigned col) const { return storage_[row * cols_ + col]; }
// Canonical swap member function.
void swap(Matrix& b) {
using std::swap;
swap(storage_, b.storage_);
swap(cols_, b.cols_);
}
// Canonical swap function. Friend name injection.
friend void swap(Matrix& a, Matrix& b) { a.swap(b); }
// This is what the compiler does for you,
// not necessary to declare these at all.
Matrix(Matrix const&) = default;
Matrix(Matrix&&) = default;
Matrix& operator=(Matrix const&) = default;
Matrix& operator=(Matrix&&) = default;
~Matrix() = default;
};

The canonical implementation of the assignment operator leverages existing functionality (copy/move ctor, dtor, andswap(); note that using a non-specialized std::swap() would be bad). It looks like this:
T& T::operator= (T val) {
val.swap(*this);
return *this;
}
It nicely avoids reimplementing otherwise existing logic. It also deals gracefully with self-assignment which is a problem in your original code (it will do work but self-assignment is generally rather uncommon; optimizing it with a check against self-assignment typically pessimizes code).
The argument is passed by value to take advantage of copy elision.
The primary caveats with this approach are below. In general I prefer the canonical implementation as it is generally more correct and the outlined issue are often not really that relevant (e.g., when the object was just created anyway the transferred memory is actually "hot").
It does not attempt to reuse already allocated and possibly "hot" memory. Instead it always uses new memory.
If the amount of held data is huge, there are copies temporarily held which may exceed system limits. Reusing existing memory and/or release memory first would both address this issue.

Related

the way of writing constructor, deconstructor and copy constructor for matrix class

I am trying to create a constructor, destructor, and copy constructor for a matrix class, and I am not so sure if I am doing it well.
In particular, I am not sure about 2 things:
Is the destructor suppose to free memory also for the memory that is allocated in the copy-constructor?
As to the line Mat[i][j]=other[i][j] (see in the code below), I wonder if I should write Mat[i][j]=other.Mat[i][j] instead?
class Matrix
{
private:
int rows;
int cols;
int **Mat;
public:
Matrix (const int &rows,const int &cols);
Matrix (const Matrix &other);
~Matrix ();
};
Matrix::Matrix(const int &n_rows,const int &n_cols) //constructor of class Matrix
{
rows=n_rows;
cols=n_cols;
Mat=new int* [cols];
for(int i =0;i<rows;i++)
Mat[i]=new int[cols];
for(int i=0;i<rows;i++)
for(int j=0;j<cols;j++)
Mat[i][j]=0;
}
Matrix::~Matrix () //destructor
{
for(int i =0;i<rows;i++)
delete Mat[i];
delete[] Mat;
}
Matrix::Matrix(const Matrix &other) //copy constructor
{
cols=other.cols;
rows=other.rows;
Mat=new int* [other.rows];
for(int i =0;i<other.rows;i++)
Mat[i]=new int[other.cols];
for(int i=0;i<other.rows;i++)
for(int j=0;j<other.cols;j++)
Mat[i][j]=other[i][j];
}
1) I think deconstructor erases only object that belongs to, because copy-constructed objects have their own destructor.
2) Yes, Mat[i][j] = other.Mat[i][j] is right, but if you want your program to be bit faster, try using pointers (it isn't easy at first time, I know, but when you get used to it isn't that hard ^^)
A constructor is a class operation that initializes an object instance of a specific class.
The object creation involves several operations such as:
Allocating memory to store the structure for the new object
Initializing the object's attributes properly.
A copy constructor is a special case of constructor, that takes an instance of the same class as an input parameter. - It is still a constructor, performing the same operations mentioned above.
A destructor is a class operation which is responsible for finalizing an object when it is not to be used anymore.
An object can be constructed using any constructor it defines, either ordinary or copy constructor. When you delete that object, any memory allocated by your class should be deallocated in the destructor.
Hope this helps.
As for your code related question; when invoking a copy constructor, you pass an existing object instance of the class. Since you don't have any operator overloading implemented, you access the object's property like you said in #2.
I am not sure if this answers your questions fully, but I hope it helps.
Yes. Anything the object still has allocated should be freed in the destructor. It doesn't matter which constructor allocated it.
Yes, you need to use Mat[i][j]=other.Mat[i][j], especially since you haven't defined any operator[] for your class.
You also need to add a copy assignment operator, per the "Rule of Three", which basically states:
If a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three.
Try this:
class Matrix
{
private:
int rows;
int cols;
int **Mat;
public:
Matrix (int n_rows, int n_cols);
Matrix (const Matrix &other);
~Matrix ();
Matrix& operator=(const Matrix &rhs);
// alternatively:
// Matrix& operator=(Matrix rhs);
};
Matrix::Matrix(int n_rows, int n_cols)
{
rows = n _rows;
cols = n_cols;
Mat = new int*[rows];
for(int i = 0; i < rows; ++i)
{
Mat[i] = new int[cols];
for(int j = 0; i < cols; ++j)
Mat[i][j] = 0;
}
}
Matrix::Matrix(const Matrix &other)
{
rows = other.rows;
cols = other.cols;
Mat = new int*[rows];
for(int i = 0; i < rows; ++i)
{
Mat[i] = new int[cols];
for(int j = 0; j < cols; ++j)
Mat[i][j] = other.Mat[i][j];
}
}
Matrix::~Matrix()
{
for(int i = 0; i < rows; ++i)
delete Mat[i];
delete[] Mat;
}
Matrix& Matrix::operator=(const Matrix &rhs)
{
if (&rhs != this)
{
Matrix temp(rhs);
std::swap(Mat, temp.Mat);
std::swap(rows, temp.rows);
std::swap(cols, temp.cols);
}
return *this;
}
// alternatively:
/*
Matrix& Matrix::operator=(Matrix rhs)
{
std::swap(Mat, rhs.Mat);
std::swap(rows, rhs.rows);
std::swap(cols, rhs.cols);
return *this;
}
*/
A better solution is to not use new[]/delete[] directly at all. Use std::vector instead, and let it handle everything for you, thus allowing your class to follow the "Rule of Zero":
Classes that have custom destructors, copy/move constructors or copy/move assignment operators should deal exclusively with ownership (which follows from the Single Responsibility Principle). Other classes should not have custom destructors, copy/move constructors or copy/move assignment operators.
class Matrix
{
private:
std::vector<std:vector<int> > Mat;
public:
Matrix (int n_rows, int n_cols);
};
Matrix::Matrix(int n_rows, int n_cols)
{
Mat.resize(n_rows);
for(int i = 0; i < n_rows; ++i)
Mat[i].resize(n_cols, 0);
/* alternatively:
Mat.resize(n_rows, std::vector<int>(n_cols, 0));
*/
}

Deep copy in a move constructor

I am new to C++11 so i still strugle with its concepts.
Here's my problem :
I have a matrix class :
class matrix
{
private:
double** data;
size_t number_lines;
size_t number_columns;
size_t capacity_lines;
size_t capacity_columns;
public:
....
}
and i've provided a copy constructor, a move constructor...
I've overloaded the multiplication operator *(double x) to multiply matrix elements by the scalar x and return the multiplied matrix.
Here's the code :
matrix matrix::operator*(double lambda)
{
double** aux_data = new double*[number_lines];
for (size_t i = 0; i < number_lines; i++)
{
aux_data[i] = new double[number_columns];
for (size_t j = 0; j < number_columns; j++)
aux_data[i][j] = lambda*data[i][j];
}
return matrix(aux_data, number_lines, number_columns);
}
the return of the function is an rvalue reference so it invokes the move constructor. Here's the code of the move constructor :
matrix::matrix(const matrix&& moved_copy)
{
if (this != &moved_copy)
{
number_columns = moved_copy.number_columns;
number_lines = moved_copy.number_lines;
data = moved_copy.data;
}
}
The problem with this move constructor is that it performs a shallow copy and not a deep copy (like every move constructor i guess, otherwise what's the point of this move constructor) so the member data points to the object pointed by moved_copy.data, but this object is local to the operator *=() function so when the operator goes out of scope the object is gone and i have a dangling pointer. So my question is : should i perform a deep copy in the move constructor or is there way of solving this problem without doing so ?
Thank you.
No, you shouldn't make a deep copy in a move constructor. The whole point of a move constructor is to take ownership of some resource that's expensive to copy.
In this case, ownership of your data pointer can be transferred from an existing matrix to the newly constructed matrix object. But the idea is to transfer ownership, to the new object, not to share ownership with the new object. In this case that just means setting moved_copy.data to nullptr, that way it won't delete your data when it's destroyed.
matrix::matrix(matrix&& moved_copy)
{
number_columns = moved_copy.number_columns;
number_lines = moved_copy.number_lines;
data = moved_copy.data;
moved_copy.data = nullptr;
}
Notice that I also removed your if guard: there's no way to construct an object from itself, so that's not really needed for a move constructor (it can be useful for a move assignment operator though).
I also removed the const from moved_copy. Move constructors need to modify the state of the moved-from object to take ownership of its resources, so const cant' be used.
Edit: It is actually possible to construct an object from itself, but it's not something that you really need to guard against.
The problem with this move constructor is that the data member has the same value on both objects after the move, such that when the first object is deleted, the second has a pointer to deleted memory.
Change the move constructor to:
matrix::matrix(matrix&& moved_copy)
{
if (this != &moved_copy)
{
number_columns = moved_copy.number_columns;
number_lines = moved_copy.number_lines;
data = moved_copy.data;
moved_copy.number_columns = 0;
moved_copy.number_lines = 0;
moved_copy.data = nullptr;
}
}
The check if (this != &moved_copy) could be omitted, because an object is normally not constructed by moving from itself.
No you should not perform a deep copy in the move constructor. Otherwise you don't gain anything and the notion of a move constructor is broken:
matrix::matrix(matrix&& moved_copy)
: data(moved_copy.data),
number_rows(moved_copy.number_rows),
number_columns(moved_copy.number_columns),
capacity_rows(moved_copy.capacity_rows),
capacity_columns(moved_copy.capacity_columns) {
moved_copy.data = nullptr;
}
Furthermore, avoid to define binary operators as member functions because you're breaking the mathematical property of commutativity. That is, although:
matrix M;
...
matrix K = m * 2.0;
will work. The following won't:
matrix M;
...
matrix K = 2.0 * m;
Prefer to define binary operators as free functions.
matrix operator*(matrix const &m, double lambda) {
matrix out(m.aux_data, m.number_rows, m.number_columns);
...
return out;
}
matrix operator*(double lambda, matrix const &m) {
return m * lambda;
}
I am new to C++11.
So you won't mind me suggesting that you implement your matrix in terms of std::vector, as then all your move concerns are solved for you:
Here's the beginning of one implementation:
#include <vector>
#include <cstddef>
#include <iostream>
class matrix
{
private:
std::vector<double> storage_;
std::size_t row_capacity_;
std::size_t rows_;
std::size_t cols_;
std::size_t get_location(std::size_t row, std::size_t col) const
{
return row * row_capacity_ + col;
}
public:
matrix(std::size_t rows, std::size_t cols, std::size_t row_capacity, std::size_t col_capacity)
: storage_(row_capacity * col_capacity)
, row_capacity_(row_capacity)
, rows_(rows)
, cols_(cols) {}
matrix(std::size_t rows, std::size_t cols)
: matrix(rows, cols, rows, cols) {}
//
// note that all move/copy operations are automatically generated.
// "The rule of none"
//
std::size_t get_rows() const { return rows_; }
std::size_t get_cols() const { return cols_; }
std::size_t get_capacity_rows() const { return row_capacity_; }
std::size_t get_capacity_cols() const { return storage_.capacity() / row_capacity_; }
double& at(std::size_t row, std::size_t col)
{
return storage_[get_location(row, col)];
}
double const& at(std::size_t row, std::size_t col) const
{
return storage_[get_location(row, col)];
}
};
int main()
{
auto m = matrix(3, 3);
m.at(1, 1) = 2;
std::cout << m.at(1, 1) << std::endl;
std::cout << m.get_cols() << std::endl;
std::cout << m.get_rows() << std::endl;
std::cout << m.get_capacity_cols() << std::endl;
std::cout << m.get_capacity_rows() << std::endl;
}

C++: Implementing move assignment operator on two dimensional array

I have a class meant to implement a matrix, here:
template<typename Comparable>
class Matrix {
private:
std::size_t num_cols_;
std::size_t num_rows_;
Comparable **array_;
public:
Matrix();
~Matrix(); // Destructor
Matrix(const Matrix<Comparable> & rhs);// Copy constructor
Matrix(Matrix<Comparable> && rhs); // Move constructor
Matrix<Comparable> & operator= (const Matrix<Comparable> & rhs);// Copy assignment
Matrix<Comparable> & operator= (Matrix<Comparable> && rhs); // Move assignment
template<typename buh> friend std::ostream &operator<< (std::ostream &os, Matrix<buh> &rhs);
void ReadMatrix();
};
(Vectors aren't an option for this particular problem.)
The array_ member in particular holds the matrix itself, and is populated using the following code:
array_ = new Comparable*[num_rows_];
for (int i = 0; i < num_rows_; ++i) {
array_[i] = new Comparable[num_cols_];
};
for(int i = 0;i < num_rows_; ++i) {
std::cout << "Enter items for row " << i << "." << std::endl;
for(int j = 0;j < num_cols_; ++j) {
std::cin >> array_[i][j];
}
}
I can fill the array with values and access them, and my copy constructor and move assignment operator are functional, but the move assignment operator throws out a strange bug. here's the definition.
template<typename Comparable>
Matrix<Comparable>& Matrix<Comparable>::operator= (Matrix<Comparable> && rhs) {
delete[] array_;
array_ = new Comparable*[rhs.num_rows_];
for(int i = 0;i < rhs.num_rows_;++i) {
std::swap(array_[i],rhs.array_[i]);
rhs.array_[i] = nullptr;
}
rhs.num_cols_ = 0;
rhs.num_rows_ = 0;
rhs.array_ = nullptr;
return *this;
}
Take the statement a = std::move(b);. If b is of a different size than a, the matrix data is deformed by the move. If b has more columns than a, the extra columns will be cut off; if b has fewer rows than a, the excess rows will remain in a; if a has more columns or rows than b, the excess columns will display memory address where there should be nothing at all. Is this a simple bug? Is there a problem with way I create the arrays? Any insight into what's causing this is appreciated.
"Move assign" doesn't mean "carefully modify the passed in object to become some sort of 'empty' value", it means "it's fine to modify the passed in object".
Move assignment here should have a very simple implementation: just swap.
template<typename Comparable>
Matrix<Comparable>& Matrix<Comparable>::operator= (Matrix<Comparable> && rhs) {
using std::swap;
swap(array_, rhs.array_);
swap(num_cols_, rhs.num_cols_);
swap(num_rows_, rhs.num_rows_);
return *this;
}
Not sure why you are using new Comparable* in your move assignment operator. The idea of a move assignment is to move the resources, not make new ones.
Your code could look like:
delete[] array_;
array_ = rhs.array_;
rhs.array_ = nullptr;
num_cols_ = rhs.num_cols_;
num_rows_ = rhs.num_rows_;
return *this;
However, consider using the copy-and-swap idiom. It's not always the most efficient option but it is a good starting point if you're not a guru.
Note: If you really want to use a pointer to pointer to implement your matrix, use vector<vector<Comparable>> instead. All the work is done for you; your code is just reinventing the wheel.
Usually it is simpler and more effective to represent a matrix with one contiguous allocation, instead of a separate allocation for each row, so you may want to give that idea some thought.

Implementing the B=f(A) syntax by move assignment

I have implemented a Matrix class with a move assignment as
template <typename OutType>
class Matrix
{
public:
int Rows_; // number of Rows
int Columns_; // number of Columns
OutType *data_; // row Major order allocation
// STUFF
Matrix<OutType> & operator=(Matrix<float>&& other) {
swap(other);
return *this;
}
void swap(Matrix<float>& other) {
int t_Rows_ = Rows_; Rows_ = other.Rows_; other.Rows_ = t_Rows_;
int t_Columns_ = Columns_; Columns_ = other.Columns_; other.Columns_ = t_Columns_;
float* t_ptr = data_;
data_ = other.data_;
other.data_ = t_ptr; }
}
in order to implement the B=f(A); syntax, as suggested in
C++: Implementing B=f(A), with B and A arrays and B already defined
As possible function, I'm considering the FFT, implemented as
Matrix<float> FFT(const Matrix<float> &in)
{
Matrix<float> out(in.GetRows(),in.GetColumns());
// STUFF
return out;
}
Is there any room for further efficiency improvements? Is there any further trick to improve, for example, the move assignment or the swap function?
EDIT: NEW SOLUTION FOLLOWING KONRAD RUDOLPH'S COMMENT
Matrix & operator=(Matrix&& other) {
std::swap(Rows_, other.Rows_);
std::swap(Columns_, other.Columns_);
std::swap(data_, other.data_);
std::cout << "move assigned \n";
return *this;
}
I recommend implementing move-assignment and move-construction for your class:
Matrix( Matrix<OutType> &&that ) noexcept
: Rows_(that.Rows_)
, Cols_(that.Cols_)
, data_(that.data_)
{
that.Rows_ = that.Cols_ = 0;
that.data_ = nullptr;
}
Matrix<OutType> &operator=( Matrix<OutType> &&that ) noexcept {
using std::swap;
swap( Rows_, that.Rows_ );
swap( Cols_, that.Cols_ );
swap( data_, that.data_ );
return *this;
}
If you implement move operations (construction and assignment) like this, std::swap should work great for your code, and you don't need to provide your own. If you do want to provide your own implementation of swap, I recommend providing it as a two-argument friend function so that it can be found through Argument Dependent Look-up. I also recommend calling swap (and all other functions) without namespace qualifications, as shown above, so that ADL is not suppressed (unless, for some reason, you really need to specify exactly which function is called, and an overload customized for the specific type would be wrong). ADL is especially valuable when dealing with templated code. If you call std::swap with the std:: qualifier, you significantly reduce the opportunity for user-defined types to provide a more efficient swap implementation.

Deleting templated C++ 2-dimensional array

Annoyance over C++'s requirement to pass a dimension in a 2-d array got me working on a templated Matrix class. I've been coding in C# for a bit, so I'm sure I'm a little rusty here.
Issue is, I get a heap exception as soon as I hit the destructor, which is trying to delete the 2-d array.
Any help gratefully accepted!
template <typename T>
class Matrix {
public:
Matrix(int m, int n) : nRows(m), nCols(n) {
pMatrix = new T * [nRows];
for (int i = 0; i < nCols; i++) {
pMatrix[i] = new T[nCols];
}
}
~Matrix() {
if (pMatrix != NULL) {
for (int i = 0; i < nRows; i++) { delete[] pMatrix[i]; }
delete[] pMatrix;
}
}
T ** GetMatrix() const { return pMatrix; }
T * Row(int i) const { return pMatrix[i]; }
inline T Cell(int row, int col) const { return pMatrix[row][col]; }
inline int GetNRows() const { return nRows; }
inline int GetNCols() const { return nCols; }
private:
int nRows, nCols;
T ** pMatrix;
};
This is the bug:
for (int i = 0; i < nCols; i++) {
pMatrix[i] = new T[nCols];
}
The loop should be until nRows, not nCols.
Other than that, let me tell you about something I did when I got tired of allocating 2-d arrays. I had to do a 3-d array. I used a map, that mapped from a coordinate - a struct holding x, y, z to the type I wanted.
I worked fast, and no need to allocate or deallocate. Assigning to a coordinate was simply done by
mymap[Coord(x, y, z)] = whatever...
Of course I needed to define the Coord struct and overload the < operator, but I found that way more comvenient than trying to allocate and deallocate a 3-d array.
Of course you will need to check if this scheme is fast enough for you. I used it to draw cells within a big cube using OpenGL and had no complaints at all.
Concerning the bug, #CodeChords_man explained it right. I have notes on implementation. I recommend to look through this wonderful FAQ post.
You should not use dynamic memory allocation unless you are 100% sure that
You really need it
You know how to implement it
I don't know of the first, and how the performance is crucial for you. But as for the second, you at least violated the rule of three. You class is very unsafe to use. If you copy it, the memory buffer will then be double-deleted.
You should not afraid to used STL containers, they are fast and optimized. At least the std::vector, it is as fast as the raw pointer in many scenarios. You can rewrite you class using std::vector as follows:
template <typename T>
class Matrix {
public:
typedef std::vector<T> MatrixRow;
typedef std::vector<MatrixRow> MatrixBody;
Matrix(int m, int n) : nRows(m), nCols(n), _body(m, MatrixRow(n)) {}
const MatrixBody& GetMatrix() const { return _body; }
const MatrixRow& GetRow(int i) const { return _body[i]; }
inline T Cell(int row, int col) const { return _body[row][col]; }
inline int GetNRows() const { return nRows; }
inline int GetNCols() const { return nCols; }
private:
int nRows, nCols;
MatrixBody _body;
};
Since this class is not using dynamic memory allocation, it is safe to copy and assign. You also don't need to explicitly store nRows and nCols in this case; you can use _body.size() and _body[0].size() instead.
Concerning underlying vector of vectors, it is dereferenced using the same [i][j] construction. It is easily iterated with begin() and end(). And if you absolutely need to use the raw pointer in some routine, you can always access it with &row[0].
The only possible difficulty is that you cannot easily convert MatrixBody to T**. But think it twice, maybe you don't really need to use T** at all.