Страницы

Поиск по вопросам

пятница, 20 декабря 2019 г.

Как надо выделять память

#cpp


Суть вопроса. Есть класс:

class a
{
private:
    int **something;

public:
    a();
    ~a();
};


Собственно, как правильно выделять память под двумерный динамический массив? А точнее,
где? Некоторые источники указывают на то, что надо не «перегружать» конструктор лишними
вычислениями и выделением памяти, а засунуть все в другой метод.

a(){something = nullptr;}
void doMemory(){ /* выделение памяти */ }
~a(){if(something != nullptr) /* высвобождаем */);}


По моим соображениям "засунуть все в другой метод" интересен, если мы создаем больше
объекты и они просто лежат в памяти и ждут своего часа, а когда этот час настает, мы
берем объект выделяем под него память и сразу работаем с ним, и он дальше лежит ждет
своего часа.
    


Ответы

Ответ 1



Когда выделять? Выделять память нужно в конструкторе, освобождать - в деструкторе. Это - реализация одной из важнейших концепций C++ - RAII (получение ресурса есть инициализация). Если ее нарушить, программа будет либо ненадежной, либо потребуется в каждом методе следить - выделена ли память. Выделять крупный блок или матрицу указателей? Зависит от размеров и от того, что нужно в дальнейшем с этой матрицей делать. Если программа работает с гигабайтами данных, и работает не один раз, а в несколько проходов (выделяя и освобождая память между ними), может оказаться, что адресное пространство сильно фрагментировано, и такого длинного куска, как вам нужно - просто нет. Для 64 бит это не так актуально, но есть на свете и 32 битные системы (в мобильниках и так далее). Если этим данным предстоит параллельная обработка на многоядерной системе с NUMA, а вы выделили память крупным блоком, эта самая NUMA вставит вам палки в колеса, когда процессоры полезут не в свои банки памяти за данными - при выделении построчно вероятность такого будет меньше. Итого: Каждый сценарий обработки данных требует индивидуального выбора способа их хранения.

Ответ 2



Начну с конца. Если конструкторов много, то вполне можно вынести общую функциональность в общий метод, и вызывать его в конструкторе (или воспользоваться новой возможностью вызова одного конструктора из другого). Логично и ваше соображение - отложить выделение памяти до того момента, когда она потребуется. Хотя с точки зрения удобства это будет немного хуже - придется все время следить за состоянием, да и удалять, возможно, не в деструкторе, а как только перестанет быть нужной. Но что касается выделения памяти для int**, то оно всегда вызывает у меня вопрос - а надо ли? Зачем вам именно двумерный массив? В конце концов, это тот же одномерный, но с пересчетом индексов. Вам совершенно необходимо обращение к нему как с [row][col]? В конце концов, никто не мешает определить оператор [], который будет возвращать указатель на нужную строку, или operator()(size_t,size_t) и обращаться к элементу массива как к a(row,col). Но это - мое личное мнение, и тут пусть выскажутся профессионалы. Но когда я вижу something = new int*[rows]; for(int i = 0; i < rows; ++i) something[i] = new int[cols]; "у меня начинает чесаться в самых нескромных местах" (с) :) Еще раз - это сугубо мое мнение. Но мне кажется, что something = new int[rows*cols]; менее, так сказать, error-prone...

Ответ 3



Мое мнение, определи для двумерного массива свой собственный тип, который будет внутри оперировать низкоуровневыми объектами. Тогда ты сможешь везде его использовать не заботясь о выделении и очистке памяти. RAII - получение ресурса есть инициализация. template class DualArray { public: DualArray(int sizeX, int sizeY) : _sizeX(sizeX), _sizeY(sizeY) { _buffer = new T[sizeX * sizeY]; } ~DualArray() { delete _buffer; } void set(int x, int y, const T &t) { _buffer[y * _sizeX + x] = t; } T get(int x, int y) const { return _buffer[y * _sizeX + x]; } private: int _sizeX, _sizeY; T *_buffer; };

Комментариев нет:

Отправить комментарий