05:02 

Трюк 0x0005 — Масштабируемый n-мерный массив

Куб 0
Ни для кого не секрет, что С++, являясь языком общего назначения, а не ориентированным на определенную предметную область, во многих аспектах содержит методы лишь начального, базового уровня. В частности, не нашедшей должного отражения в синтаксисе языка является тема динамически масштабируемых многомерных массивов. Идея внести в программу двумерный масштабируемый массив вызовет у программиста тревогу, трехмерный — раздражение, а при мысли о четырехмерных и прочих n-мерных массивах программист С++, скорее всего, посоветует заказчику проекта ориентироваться на другой язык программирования.
Однако, не все настолько мрачно. Хорошая новость заключается в том, что рекурсивное наследование шаблонов и перегрузка operator[] могут позволить изготовить класс, поддерживающий n-мерные массивы с возможностью их масштабирования, и всего того, что пожелает разработчик.
Для примера ниже приведен класс Array, позволяющий создавать массивы любого типа и любой размерности.

//------------------------------------------------------------
#include <string>
#include <vector>
#include <stdarg.h>
#include <algorithm>
template<class T, int Dim>
class Array {
// объявляем дружественным для вызова простого конструктора
friend class std::vector< Array<T, Dim> >;
// пользователю вряд ли нужно создавать массив без размера,
// поэтому в private
Array() {
m_Data=0;
}
public:
Array(int Sizes[Dim]) {
Resize(Sizes);
m_Data=0;
}
Array(int Size0, ...) {
va_list Marker;
va_start(Marker, Size0);
int Sizes[Dim];
Sizes[0]=Size0;
for (int Index=1; Index<Dim; Index++) {
Sizes[Index]=va_arg(Marker, int);
}
va_end(Marker);
Resize(Sizes);
m_Data=0;
}
Array(const Array<T, Dim> &Ref) {
m_Vector=Ref.m_Vector;
m_Data=(Ref.m_Data) ? new T(*Ref.m_Data) : 0;
}
~Array() {
delete m_Data;
}
// динамическое масштабирование массива
void Resize(int Sizes[Dim]) {
if (Dim!=0) {
m_Vector.resize(Sizes[0]);
for (int Index=0; Index<Sizes[0]; Index++) {
m_Vector[Index].Resize(Sizes+1);
}
}
}
Array<T, Dim> operator= (const Array<T, Dim> &Ref) {
// перед присвоением сжимаем массив до нулевых размеров
if (Dim!=0) {
// ставим Dim+1, чтобы компилятор не выдавал ошибку при Dim==0
int Sizes[Dim+1];
std::memset(Sizes, 0, Dim*sizeof(int));
// будут вызваны деструкторы для всех элементов
Resize(Sizes);
}
return Array<T, Dim>(Ref);
}
Array<T, Dim> &operator= (const T &Value) {
if (Dim==0) {
delete m_Data; // удаляем старое значение
m_Data=new T(Value); // создаем новое
}
return *this;
}
Array<T, Dim-1> &operator[] (int Index) {
// как и положено в С++, корректность индекса не проверяем :)
return m_Vector[Index];
}
operator T&() {
// неявное преобразование типа, работает только для Dim==0,
// иначе ошибка
return *m_Data;
}
private:
std::vector< Array<T, ((Dim-1)>0 ? (Dim-1) : 0) > > m_Vector;
T *m_Data;
};
// примеры использования
int main() {
Array<int, 2> data(7, 7); // двумерный массив int 7х7
int Sizes[2]={5, 5};
data.Resize(Sizes); // превращаем его в 5х5
data[1][2]=-1; // индексирование работает как для lvalue
int x=data[1][2]; // так и для rvalue
data[3][3]=11;
x=data[3][3];

// пятимерный массив double 7х7х7х7х7
Array<double, 5> double_arr(7, 7, 7, 7, 7);
double_arr[0][0][2][3][1]=11;

// копируем двумерный массив data в data2
Array<int, 2> data2(data);
Array<int, 2> data3(2, 1);
data3[0][0]=15;
data3=data; // присваиваем data3 массив data

// четырехмерный строковый массив из 1 элемента
Array<std::string, 4> str(1, 1, 1, 1);
str[0][0][0][0]="xe"; // присваиваем значение

// некорректно, вызывает ошибку выполнения, так как массив двумерный
// x=data[2];
// некорректно, вызывает ошибку выполнения
// Array<char *, 4> Quad(data);

return 0;
}
//------------------------------------------------------------

@темы: Трюки с шаблонами

URL
   

Записная книжка программиста C++

главная