Выделение динамической памяти в конструкторе класса.
Деструктор класса, освобождение динамически выделенной памяти в деструкторе.
Динамически выделяемая память всегда требует особого внимания по сравнению с автоматической памятью.
Динамически создаваемые объекты и массивы объектов требуют операции выделения памяти, а в тот момент как этот объект или массив объектов становится ненужным, требуется явный вызов операции высвобождения выделенной памяти, в этом заключается принцип работы с динамической памятью, а именно весь процесс по управлению возлагается на программиста.
Если забыть выполнить выделение или высвобождение памяти, то это рано или поздно приведет к краху программы.
Работа с динамической памятью осуществляется через указатели, если забыть выделить память, то работая через не проинициализированный указатель можно попортить чужую память.
А если забывать освобождать динамически выделенную память на момент утраты необходимости в ней, то можно очень скоро получить невозможность выделить новую область памяти под другие объекты, так как вся доступная процессу память забита ненужными, висячими объектами или массивами.
Прежде чем вдаваться в подробности обработки динамических полей класса, вспомним как осуществляется выделение и высвобождение динамической памяти.
Оператор new выделяет запрошенное количество памяти и возвращает указатель на выделенную область памяти.
Оператор delete высвобождает память из под динамического объекта.
Оператор delete [] высвобождает память из под динамического массива.
Каких особых телодвижений требуют динамические поля класса?
Список того, что требуют динамические поля, следующий:
— в момент создания объекта-экземпляра класса, должно происходит выделение динамического блока памяти.
— в классе должно быть предусмотрено дополнительно поле в котором будет храниться размерность выделенного блока (обычно, количество элементов динамического массива, но иногда и количество байтов).
— в момент удаления объекта, связанная с ним динамическая память должна быть высвобождена.
Для реализации первого и второго пункта из представленного выше списка, необходимо в классе предусмотреть конструктор, в котором выполняется выделение требуемого объема динамической памяти и описать два обязательных поля: указатель на динамическую память и целочисленное поле для хранения кол-ва элементов динамического массива (поля).
Рассмотрим пример описания такого класса, класса SmartArray:
Динамическое поле необязательно должно быть типа char *, Вы вольны использовать любой другой тип.
Конструктор данного класса должен быть с параметром, это требуется по той причине, что при создании экземпляра, динамическому полю требуется знать сколько именно элементов ему хранить.
Конечно, есть возможность описать конструктор по умолчанию для данного класса, следующим образом:
В таком случае, динамическому полю присваивается значение nullptr (пустой указатель), а поле — размерность массива, обнуляется. Так как был описан конструктор по умолчанию, то для работы с экземплярами данного класса требуется предусмотреть метод для проверки динамического поля на пустоту, для этого и был реализован метод is_empty.
Осталось только предусмотреть правильное высвобождение памяти из под динамического поля в момент удаления экземпляра класса.
И как же перехватывать момент удаления экземпляра?
А для этого служит специальный метод — деструктор класса, он такой же особенный как и конструктор, его не нужно вызывать, он вызывается автоматом в момент удаления объекта и вызывается для конкретного экземпляра только один раз.
В данном методе описывается правильное высвобождение динамической памяти, если же все поля класс автоматические, то тело деструктора остаётся пустым.
Пример описания деструктора для нашего класса SmartArray:
Обратите внимание на синтаксис описания деструктора, у него не указывается тип возвращаемого параметра, имя деструктора состоит из символа тильда
и имени класса (без пробела между тильдой и именем класса), у деструктора список входных параметров всегда пуст.
Любой класс может содержать только один деструктор!
В самом деструкторе, как Вы можете наблюдать, содержится вызов метода delete[], который и выполняет высвобождение занятой памяти из под динамического поля.
Заметка
В описанном деструкторе класса можно было перед вызов оператора delete[] выполнить проверку: «а не является ли массив пустым?»:
Но этого не было сделано по причине того, что оператор delete[], вызванный для указателя равного nullptr, будет выполнен правильно, без формирования какой либо ошибки.
Мы рассмотрели все основные моменты обработки динамических полей, осталось научиться обращаться к отдельным элементам динамического поля.
Обращение к элементам динамического поля можно реализовать через метод?
Можно, но для этого лучше воспользоваться специальным оператором [].
Как ранее было сказано, класс позволяет описать тип в котором содержится набор полей, методов и операторов, так что, нет ничего сверхъестественного в описание оператора [] для нашего класса.
Необходимо знать следующее:
— в классе можно описать (переопределить) только поддерживаемые в С++ операторы, своих напридумывать нельзя
— переопределение операторов (в отличии от реализации пользовательских методов) производится четко, в соответствии с правилами стандарта С++
Синтаксис переопределения оператора [] следующий:
тип_элемента_массива& opeator[] (int index)* тело оператора */>
Оператор является функцией-членом класса, он возвращает ссылку на объект (на элемент массива), тип которого соответствует типу элемента динамического массива (поля).
Оператор [] принимает только один аргумент, данный аргумент может быть любого типа (int, size_t и т.д.) и обязан определять индекс целевого элемента динамического поля (массива).
При описании данного оператора, в его теле можно реализовать проверку выхода заданного (в качестве аргумента) индекса за границы массива, как это сделано в следующем примере:
Как Вы можете заметить, в класс добавилось дополнительное поле err_value, это поле хранит значение, которое будет возвращаться оператором [] в случае выхода индекса за пределы массива.
Никто не запрещает убрать проверку выхода за границы массива из оператора [], для данного примера это будет даже лучше, так как избавляет нас от описания костылика в виде дополнительного поля err_value, но при этом, проверку придется выполнять во внешних функциях, в которых осуществляется обработка экземпляров данного класса.
Не забудьте в данном классе реализовать метод size(), который будет возвращать кол-во элементов динамического поля.
Может ли быть массив свойством класса?
Если может быть, то как на примере массива строк это все можно организовать?
![]()
Малость добавлю от себя. Если вы пишете код в Visual Studio, то очень удобно пользоваться так называемыми снипетами. Для создания свойств существует несколько снипетов:
- наберите prop и дважды нажмите на клавишу Tab у вас появится заготовка свойства public int MyProperty < get; set; >между int и MyProperty можно переключаться с помощью все той же Tab . Так вот, когда вы воспользуетесь этим снипетом, то курсор у вас будет стоять в позиции int сразу начинайте вводить List<string> после нажмите Tab и вы сможете дать нужное вам название свойству, чтобы закончить нажмите клавишу ввода.
- propg даст public int MyProperty
![]()
как и для всего остального
![]()
из комментария @Vladislav Khapin
Всё ещё ищете ответ? Посмотрите другие вопросы с метками c# массивы или задайте свой вопрос.
Site design / logo © 2022 Stack Exchange Inc; user contributions licensed under cc by-sa. rev 2022.6.10.42345
Нажимая «Принять все файлы cookie», вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Массив как поле класса c
Здравствуйте, Аноним, Вы писали:
AG>>Кроме статического члена класса, массив-член класс не может иметь инициализатор через фигурные скобки.
А>В какой книге это написанно?
Вот:
Поля-массивы в классе
В С++ нет запрета на объявление в классе поля-массива. Естественно, размер класса с полем-массивом увеличивается на размер массива. Однако использование массива в классе недостаточно хорошо отражено в стандарте. Это вызывает многочисленные вопросы, особенно связанные с инициализацией. Мы начнем разбираться с этими вопросами на самом простом примере, в котором объявляются и инициализируются несколько полей-массивов (листинг 4.1).
Листинг 4.1. Поля-массивы в классе
В классе Arrays объявлено три поля-массива: m0, m1 и m2. Как видим, задать количество элементов массива можно либо явной константой, либо константным выражением со статическими и (или) перечислимыми константами, причем константы должны быть определены раньше массива. Задавать количество элементов поля-массива обязательно.
Нельзя задать количество элементов как значение другого поля, заполняемого конструктором, даже если это поле константное. Например, пусть в классе объявлены следующие поля:
В этом случае система Visual C++.NET 2003 выдает ошибку компиляции C2327, которая сигнализирует о том, что k в данном случае не является ни статической константой, ни константой перечислимого типа.
Конструктор без аргументов Array() обнуляет массивы, выполняя цикл в теле. Такой способ инициализации поля-массива — в теле конструктора — является наиболее простым и позволяет присваивать элементам массива любые значения. Однако наиболее часто выполняется обнуление, поэтому для массивов разрешается применять инициализацию нулем. Тогда наш конструктор выглядит значительно проще:
Для массива объектов некоторого типа такая запись означает вызов конструктора по умолчанию (без аргументов), и не забудьте, что этот вызов выполняется для каждого элемента массива.
К сожалению, применение списков инициализации для полей-массивов этим и ограничивается — в скобках нельзя ничего указывать. Например, при написании следующей конструкции система Visual C++.NET 2003 выдаст сообщение об ошибке С2536, сигнализируя о том, что явная инициализация массивов запрещена:
Попытки написать в скобках некоторое одиночное выражение (например, вызов функции, заполняющей массив) компилятор отвергает по той же причине.
Явная инициализация не проходит даже для символьных массивов. Например, объявим в классе Arrays символьный массив:
После этого попытаемся в списке инициализации конструктора присвоить этому массиву символьную константу:
Тогда в системе Visual C++.NET 2003 мы получим сообщение об ошибке:
error C2536: ‘Arrays::Arrays::s’ :
cannot specify explicit initializer for arrays
Это сообщение говорит о том, что нельзя определять явный инициализатор для массивов.
Тем не менее, можно задать в классе указатель на символ:
Затем можно инициализировать указатель символьной константой в списке инициализации конструктора, как было показано ранее.
Как это ни странно, но большие проблемы возникают при попытках объявить в классе константный массив встроенного типа! Как мы уже выяснили, константы нельзя инициализировать в теле конструктора, значения им присваиваются только в списке инициализации конструктора. Однако для константного массива встроенного типа не работает даже инициализация нулем.
ПРИМЕЧАНИЕ
Этот вопрос практически не отражен в стандарте, поэтому компиляторы ведут себя по-разному. В системе Visual C++.NET 2003 выдается ошибка компиляции C2439, а Borland C++ Builder 6 выдает только предупреждение W8038 о том, что массив не инициализируется.
Не проходит и отмена константности. Например, зададим массив m0 как константный, а в теле конструктора определим инициализацию в цикле:
Однако и Visual C++.NET 2003, и Borland C++ Builder 6 отказываются компилировать такой цикл.
Но для константного массива из объектов не встроенного типа задавать инициализацию нулем разрешается. Для этого в классе должен быть определен конструктор без аргументов, который вызывается для инициализации каждого элемента константного поля-массива. Например, вполне можно инициализировать константный массив денег:
const TMoney ss[10];
Для этого достаточно задать в списке инициализации конструктора инициализацию нулем ss(). Как реально инициализируется такой массив, конечно, зависит от реализации конструктора по умолчанию.
Статические поля-массивы
Методы перевода перевода регистра в классе, реализующем строки, должны обеспечивать перевод и русских, и английских букв. Простейшая реализация методов преобразования в верхний и нижний регистры может быть такой, как в листинге 4.19. Поскольку перевод регистра — разовое действие, то пусть обе операции возвращают значение.
Листинг 4.19. Простейшая реализация методов изменения регистра строки
Методы прекрасно работают, однако таком виде они нас, конечно, не устраивают — любой программист тут же скажет, что использование локальных массивов очень неэффективно. Массивы заново создаются при каждом входе в функцию — на это тратится и время и место при выполнении программы.
Первое побуждение — вынести массивы из методов и объявить два поля-массива. Но тут нас ожидают некоторые сложности. Во-первых, поля нельзя объявить константными, хотя по сути своей они таковыми и являются. Дело в том, что тогда мы не сможем эти поля инициализировать — константные массивы встроенного типа никаким образом не инициализируются . Но если массивы не константные, то их придется заполнять в каждом конструкторе, написав для этого приватную функцию.
Во-вторых, и это гораздо важнее, неконстантные поля-массивы существенно увеличивают размер класса — на 118 байт. Пока объектов немного, это не доставляет беспокойства. Но представьте, что нам надо объявить массив размером в 1000 элементов типа TString. Ситуация отнюдь не надуманная, так как подобный массив, например, в качестве буфера может использовать простой текстовый редактор. Но такой массив имеет уже 118 000 лишних байтов, которые еще и заполняются во время создания — ведь конструктор вызывается для каждого элемента массива. Ситуация совершенно неприемлемая как с точки зрения расхода памяти, так и с точки зрения эффективного выполнения программы.
Так как массивы у нас символьные, то можно было бы обойтись указателями на символ — тем более, что их можно инициализировать символьной константой. Ситуация с памятью несколько улучшается: два указателя занимают в классе всего 8 байт. Однако указатели тоже будут инициализироваться для каждого создаваемого объекта.
Массивы нужно все-таки иметь в единственном экземпляре; они, очевидно, должны быть объявлены константными и проинициализированы один раз при объявлении. Мы можем это сделать, если объявим их глобальными. Однако такое решение совершенно неприемлемо с точки зрения инкапсуляции — глобальные переменные излишне доступны и их использование способствует появлению трудноуловимых ошибок. Массивы по сути своей являются (и должны быть) частью класса TString.
В языке С++ есть средство, позволяющее сделать именно то, что нам требуется: объявить массивы в единственном экземпляре, сделать их константными, проинициализировать при объявлении и, тем не менее, локализовать их в классе. Такие константные поля-массивы надо объявить в классе статическими (см. п.п. 9.4.2 в [1]):
Инициализация (определение) статических полей выполняется вне класса:
Этот оператор присваивания является определением статического члена класса (а в классе — только объявление). Такой оператор присваивания — единственная форма инициализации константных статических массивов (и любых других константных статических полей встроенного нецелочисленного типа). Обратите внимание, что слово static в операторе инициализации отсутствует — его там писать нельзя. Статические массивы, как и статические константы (и вообще любые статические поля), являются частью класса, создаются в единственном экземпляре при запуске программы (до создания любых объектов класса), поэтому их нельзя инициализировать в конструкторе.
ПРИМЕЧАНИЕ
Помимо статических полей, в классе можно объявлять и статические методы. Такие методы являются методами класса и могут вызываться до создания первого объекта этого класса.
Cтатические константы места в классе не занимают. Точно так же не увеличивают размер класса и любые статические поля — они хранятся вне класса. Но принцип инкапсуляции выполняется — доступ к таким полям имеют только методы и «друзья» класса. Наши методы перевода регистра упрощаются (листинг 4.20).
Листинг 4.20. Реализация методов перевода регистра