/* myvector.h */
#include <algorithm>
class myvector
{
int sz;
T * data;
…
public:
void resize(int nsize)
{
T* ndata = new T[nsize];
int n = (sz < nsize) ? sz : nsize;
std::copy(data, data + n, ndata);
delete[] data;
data = ndata;
sz = nsize;
}
};
Матрица — вектор векторов.
Допустим нам необходимо создать матрицу 3x4, тогда мы можем попробовать реализовать её следующим образом.
/* main.cpp */
int main()
{
myvector<myvector<int>> m(3);
…
}
Но так сделать не получится, потому что произойдёт ошибка компиляции: мы не определили конструктор по умолчанию. Необходимо добавить в класс myvector
следующую строку.
/* myvector.h */
class myvector
{
…
public:
myvector(int size = 0);
};
Теперь мы имеем 3 экземпляра класса myvector
с размером 0.
Чтобы увеличить размер всех векторов выполним для каждого из них resize()
.
/* main.cpp */
int main()
{
…
for(int i = 0; i < 3; i++)
m[i].resize(4);
m[1][2] = 777;
}
В данном случае значение элемента будет присваиваться по ссылке, поэтому передаваемый объект будет очень маленьким.
Рассмотрим, как данная матрица будет выглядеть в памяти.
Будет ли данная динамическая память возвращена системе?
При выходе из блока { … m[1][2] = 777; }
, будет вызван деструктор ~m()
выполняющий delete[] data
.
Замечание. Если data
является массивом объектов некоторого класса, тогда до возврата своей памяти он вызывает деструкторы всех элементов этого массива.
То есть сначала будут освобождены массивы int
, а за тем массив data
.
/* matrix.h */
template<typename T>
class matrix
{
// Так сделать не получится
// myvector<myvector<T>> mdata(3);
// Необходимо писать так
myvector<myvector<T>> mdata;
public:
// Вызывать конструктор mdata(m) в теле конструктора
// matrix уже поздно, а при объявлении еще рано, поэтому
// конструктор необходимо вызывать в списке инициализации
matrix(int m, int n): mdata(m)
{
for(int i = 0; i < m; i++)
mdata[i].resize(n);
}
};
Если в списке инициализации мы забыли вызвать конструктор подобъекта являющегося объектом другого класса, то сгенерируется код вызывающий конструктор этого объекта по умолчанию.
Деструктор для данного класса писать не надо.
Будет сгенерирован деструктор по умолчанию ~matrix() { }
.
Правило. Деструкторы всех подобъектов вызываются автоматически в эпилоге (перед закрытием фигурной скобки деструктора основного объекта). Первым вызовется деструктор основного объекта. Деструкторы вызываются в порядке обратном порядку вызова конструкторов. То есть последовательность вызовов конструкторов и деструкторов будет следующая.
m[1, 2]
— в C++ так писать нельзя
m[1][2]
— класс matrix не контролирует [2]
Поэтому лучше всего перегрузить operator()
, тогда обращение по индексу будет выглядеть следующим образом m(1, 2)
.
/* matrix.h */
template<typename T>
class matrix
{
…
public:
…
T& operator()(int i, int j)
{
return mdata[i][j];
}
};
Так как operator()
будет inline
-функцией, то в результате вызов m(1, 2)
будет преобразован следующим образом:
m(1, 2)
~ m.mdata[1][2]
~ m.mdata.data[1].data[2]
Их писать не нужно.
Они сгенерируются автоматически и будут работать правильно.
Конструктор копии и operator=
необходимо писать вручную, только если мы в конструкторе данного класса выделяем динамическую память, а в деструкторе ее явно возвращаем.
Если в классе есть подобъект, который берет на себя все функции, то определять эти члены не нужно.
Как работают автоматически сгенерированные конструктор копии и операция присваивания?
Автоматически сгенерированный конструктор копии вызывает конструкторы копий для всех своих полей. Автоматически сгенерированная операция присваивания вызывает операции присваивания для всех своих полей.
Посмотрим, какой будет сгенерирован конструктор копии.
/* matrix.h */
template<typename T>
class matrix
{
…
public:
…
matrix(const matrix<T> & mm): mdata(mm.mdata) {}
};