Say I have a (immutable) matrix class that dynamically creates an array in the constructor, and deletes it in the deconstructor.
template <typename T>
class matrix {
private:
T* data;
public:
size_t const rows, cols;
matrix(size_t rows, size_t cols) : rows(rows), cols(cols) {
data = new T[rows*cols];
}
~matrix() {
delete [] data;
}
//access data
T& operator()(size_t row, size_t col) {
return data[row*cols + col];
}
matrix<T>& operator=(const matrix<T>& other) {
//what will this->data contain? do I need to delete anything here?
//should I call the constructor?
rows = other.rows;
cols = other.cols;
data = new T[rows*cols];
std::copy(&data[0],&data[0] + (sizeof(T)*rows*cols),&other.data[0]);
return *this;
}
}
Because I don't have a default constructor within the operator=
function, the data in this
is just garbage, right? Even if I had a default constructor would it be called? I'm basing the above code off of the example here. Note that I have left out input validation/bounds checking for brevity.
EDIT: I would like to clarify that I'm only concerned for a call like this:
matrix<int> A = B;
Where B has already been initialized.
Say I have a (immutable) matrix class that dynamically creates an array in the constructor, and deletes it in the deconstructor.
You are violating the Rule of Three by not implementing a copy constructor (and the Rule of Five in C++11 by not implementing a move constructor and a move assignment operator).
Your copy assignment operator has a memory leak, as it is not delete[]
'ing the old data
array before assigning the new[]
'ed array.
Try this instead:
template <typename T>
class matrix {
private:
T* data;
size_t const rows, cols;
public:
matrix(size_t rows, size_t cols) : rows(rows), cols(cols) {
data = new T[rows*cols];
}
matrix(const matrix<T> &src) : rows(src.rows), cols(src.cols) {
data = new T[rows*cols];
std::copy(data, &data[rows*cols], src.data);
}
/* for C++11:
matrix(matrix<T> &&src) : rows(0), cols(0), data(nullptr) {
std::swap(rows, src.rows);
std::swap(cols, src.cols);
std::swap(data, src.data);
}
*/
~matrix() {
delete [] data;
}
T& operator()(size_t row, size_t col) {
return data[(row * cols) + col];
}
T operator()(size_t row, size_t col) const {
return data[(row * cols) + col];
}
matrix<T>& operator=(const matrix<T>& other) {
if (&other != this) {
delete[] data;
rows = other.rows;
cols = other.cols;
data = new T[rows*cols];
std::copy(data, &data[rows*cols], other.data);
}
return *this;
}
/* for C++11:
matrix<T>& operator=(matrix<T> &&other) {
delete[] data;
data = nullptr;
rows = cols = 0;
std::swap(rows, other.rows);
std::swap(cols, other.cols);
std::swap(data, other.data);
return *this;
}
*/
};
However, the copy-and-swap idiom would be safer:
template <typename T>
class matrix {
private:
T* data;
size_t const rows, cols;
public:
matrix(size_t rows, size_t cols) : rows(rows), cols(cols) {
data = new T[rows*cols];
}
matrix(const matrix<T> &src) : rows(src.rows), cols(src.cols) {
data = new T[rows*cols];
std::copy(data, &data[rows*cols], src.data);
}
/* for C++11:
matrix(matrix<T> &&src) : rows(0), cols(0), data(nullptr) {
src.swap(*this);
}
*/
~matrix() {
delete [] data;
}
T& operator()(size_t row, size_t col) {
return data[(row * cols) + col];
}
T operator()(size_t row, size_t col) const {
return data[(row * cols) + col];
}
void swap(matrix<T>& other) noexcept
{
std::swap(rows, other.rows);
std::swap(cols, other.cols);
std::swap(data, other.data);
}
matrix<T>& operator=(const matrix<T>& other) {
if (&other != this) {
matrix<T>(other).swap(*this);
}
return *this;
}
/* for C++11:
matrix<T>& operator=(matrix<T> &&other) {
other.swap(*this);
return *this;
}
*/
};
In the latter case, the copy assignment and move assignment operators can be merged together into a single operator in C++11:
matrix<T>& operator=(matrix<T> other) {
other.swap(*this);
return *this;
}
Or, you could just follow the Rule of Zero by using std::vector
and let the compiler and STL do all of the work for you:
template <typename T>
class matrix {
private:
std::vector<T> data;
size_t const rows, cols;
public:
matrix(size_t rows, size_t cols) : rows(rows), cols(cols), data(rows*cols) {
}
T& operator()(size_t row, size_t col) {
return data[(row * cols) + col];
}
T operator()(size_t row, size_t col) const {
return data[(row * cols) + col];
}
};
Because I don't have a default constructor within the
operator=
function, the data in this is just garbage, right?
No, because operator=
can only be called on a previously constructed object, just like any other class instance method.
Even if I had a default constructor would it be called?
In the example you showed, no.
I would like to clarify that I'm only concerned for a call like this:
matrix<int> A = B;
That statement does not call operator=
at all. The use of =
is just syntax sugar, the compiler actually performs copy construction as if you had written this instead:
matrix<int> A(B);
Which requires a copy constructor, which you have not implemented, and the compiler-generated copy constructor is not sufficient to make a deep copy of your array.
Copy assignment would look more like this instead:
matrix<int> A; // <-- default construction
A = B; // <-- copy assignment
matrix<int> A(B); // <-- copy construction
A = C; // <-- copy assignment