I am creating my own Matrix class. I wrote a copy constructor, but it works correctly only for primitive classes (namely, memory allocation for data). How can the constructor be rewritten (it is possible to overload the new operator) so that it works correctly not only for primitive data types, but also for complex structures (for example, complex classes).
Class fields:
template <typename T>
class Matrix
{
private:
T *data = nullptr;
size_t rows;
size_t cols;
Copy constructor:
Matrix(const Matrix &other) : data(new T[other.rows * other.cols]), rows(other.rows), cols(other.cols)
{
for (int i = 0; i < rows * cols; i )
{
data[i] = other.data[i];
}
}
It is forbidden to use STL containers, all memory management is manually
Destructor
~Matrix() {
rows = 0;
cols = 0;
delete[] data;
}
CodePudding user response:
You can simply make the memory management automatic:
#include <cstddef>
template <typename T, std::size_t rows, std::size_t cols>
class Matrix {
public:
Matrix() { }
T * operator[](std::size_t x) {
return data[x];
}
private:
T data[rows][cols]{};
};
But if you must use dynamic memory allocations:
#include <cstddef>
#include <type_traits>
#include <cstring>
#include <initializer_list>
#include <utility>
template <typename T, std::size_t rows, std::size_t cols>
class Matrix {
private:
T *data{new T[rows * cols]};
public:
Matrix() { }
~Matrix() {
delete[] data;
data = nullptr; // crash on use after free
}
template<typename U>
Matrix(std::initializer_list<std::initializer_list<U>> list)
: data (static_cast<T*>(operator new[](sizeof(T) * rows * cols, static_cast<std::align_val_t>(alignof(T))))) {
std::size_t i = 0;
for (auto &lst : list) {
std::size_t j = 0;
for (auto &t : lst) {
std::construct_at<T>(&(*this)[i][j], std::forward<const U>(t));
j;
}
i;
}
}
Matrix(const Matrix &other) {
*this = other;
}
Matrix(T &&other) : data(other.data) {
other.data = nullptr;
}
Matrix & operator=(const Matrix &other) {
if constexpr (std::is_aggregate_v<T>) {
memcpy(data, other.data, sizeof(T) * rows * cols);
} else {
for (std::size_t i = 0; i < rows; i) {
for (std::size_t j = 0; j < cols; j) {
(*this)[i][j] = other[i][j];
}
}
}
return *this;
}
Matrix operator=(Matrix &&other) {
swap(data, other.data);
return *this;
}
const T * operator[](std::size_t x) const {
return &data[x * cols];
}
T * operator[](std::size_t x) {
return &data[x * cols];
}
};
#include <iostream>
int main() {
Matrix<int, 2, 2> a{{{1, 2}, {3, 4}}};
Matrix<int, 2, 2> b{a};
std::cout << b[0][0] << " " << b[0][1] << std::endl;
std::cout << b[1][0] << " " << b[1][1] << std::endl;
}
Note: This is a column Matrix. If you want a Row matrix you have to swap indexes around.
CodePudding user response:
The problem is your try to allocate memory for the objects:
: data(new T[other.rows * other.cols])
This default-constructs new objects of type T
right away, to which you later on assign values. However it relies on the objects being default constructable, if they aren't, it cannot compile. So you will need to allocate memory explicitly, create the objects via placement new and later on destroy them explicitly alike.
Just a minimalistic demo – you'll need to place the right things at the right places within your constructors and destructor:
template<typename T>
void demo(size_t n, T t[])
{
auto c = static_cast<T*>
(
operator new[]
(
n * sizeof(T), static_cast<std::align_val_t>(alignof(T))
)
);
for(size_t i = 0; i < n; i)
{
// placement new
new (c i) T(t[i]);
}
for(size_t i = n; i--; )
{
// explicitly call destructor
c[i].~C();
}
operator delete[](c);
}
Just the very basics – note that this doesn't consider exceptions occurring during construction/destruction, you'll need appropriate handling for as well.