Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 64
Текст из файла (страница 64)
Этот список приблизительно отсортирован в порядке важности. В следующих подразделах подробно рассматриваются все перечисленные способы создания объектов и их использование. 10.4.4. Локальные объекты Конструктор локального объекта выполняется каждый раз, когда поток управления работающей программы проходит через его определение. Деструктор же вызывается при каждом выходе из блока, содержащего указанное объявление. Деструкторы локальных объектов выполняются в порядке, обратном выполнению их конструкторов.
Например: гаЫ Т(афпг 1) ( ТаЫе аа; ТаЫе ЬЬг (У(1>0) ( ТаЫе сс; 308 Глава ) О. Классы ,У... ) ТаЫе ~Ы; уу ... Здесь аа, ЬЬ и зЫ конструируются (в указанном порядке) каждый раз при вызове функции 1'( ), и гЫ, ЬЬ и аа уничтожаются (в указанном порядке) при каждом выходе изб() . Если же в конкретном вызове 1>0, сс конструируется после ЬЬ, и уничтожается перед конструированием й1. 10.Я.4.1.
Копирование объектов Если г1 и 12 являются объектами класса ТаЫе, то присваивание г2=И по умолчанию означает почленное (побитовое) копирование г1 в 12 (510.2.5). Такое поведение операции присваивания может вызывать проблемы для объектов классов, содержаших указатели в качестве членов. Почленное копирование не соответствует семантике копирования объектов классов, управляюшнх ресурсами с помошью пары конструктор/деструктор. Например: гоЫ Ь () ( ТаЫе (1; ТаЫе 12 = 111 УУ копирующая инициализация: проблема ТаЫе 13; 13 = 12; У копирующее присваивание: проблема с1авв ТаЫе ( зУ ...как в 110.411...
ТаЫе (сопи ТаЫеь ); ТаЫеа прего<ос= (сопи ТаЫев); УУ копирующий конструктор УУ присваивание Умолчательный конструктор класса ТаЫе вызывается дважды — при конструировании объектов 11 и 13; для объекта 12 он не вызывается, так как тут работает копирование (из 11 в 12). В то же время деструктор класса ТаЫе вызывается трижды — при уничтожении объектов 11, г2 и г3! Согласно умолчательной трактовке операции присваивания перед выходом из функции Ь() каждый из объектов 11, 12 и 13 будет содержать указатель на массив имен, память под который была динамически выделена в свободной памяти в момент создания 11.
Указатель же на массив имен, выделенный в свободной памяти при создании 13, не сохранился из-за того, что была выполнена операция 13=12 с ее почленным копированием. Ввиду отсутствия автоматической сборки мусора (яагЬаяе собесбоп; в!0.4.5), эта память потеряна для программы навсегда. С другой стороны, массив, выделенный для 11, теперь адресуется также из г2, г3, и в итоге он будет удаляться трижды. Результируюший эффект стандартом не определен, но почти наверняка будет катастрофическим. Подобного рода аномалий можно избежать, если явно определить, что же нужно понимать под присваиванием объектов класса ТаЫе: 309 10.4.
Объекты Программист волен определить любое приемлемое поведение для операции присваивания, однако традиционным является копирование содержимого контейнера (или создание иллюзии у пользователя о таком копировании; см. 911.12). Например: У копирующий конструктор ТаЫе: . "Тайе (свив! Тайен 1) ( р = иен Мате [лг=!.лг]; (вг(1и! 1=0; 1<ей и+) р [П =! р [11 ) Тайеа ТаЫе:: орегагвг= (свит ТаЫеа !) гг приеваивание ( ф'(гва! = аг) ~Уне забудьте о возможности самоприсваивания: г = т ( ве(ег[] р; р = пен )уате[вг=!.вг]; Гог(!нг 1=0; !<вен 1++) р [1] =!.р [1]; ) гетиги * ЙЬ; ) Копирующий конструктор (сору сопл!гас!ог) и операция присваивания (агв(яптеп!) различаются в следуюшем принципиальном моменте: копирующий конструктор инициализирует «сырую» (гатг — ранее не инициализированную) память, в то время как операция присваивания работает над участком памяти, содержащем корректно сконструированный классовый объект.
В ряде случаев допускаются разные схемы оптимизации, но основная стратегия работы операции присваивания проста: защита от присваивания самому себе, удаление старых элементов, инициализация и копирование новых элементов. Как правило, нужно скопировать все нестатические члены (510.4.6.3). Для сообщения об ошибках копирования можно использовать исключения 514.4.б.2). По поводу техники написания операций копирования, оставляющих левый операнд в корректном состоянии при возникновении исключений (ехсерйоп-заГе сору орега(юпз), см.
вЕ.З.З. 10.4.5. Динамическое создание объектов в свободной памяти Конструктор для объекта, создаваемого в свободной памяти, вызывается по операции пеп. Объект в свободной памяти существует до тех пор, пока не будет вызвана операция т!е!е!е с операндом, являющимся указателем на этот объект. Рассмотрим следующий пример: 1и! та!и () ( Тайе* р = пеш ТаЫе; ТвЫе* а = пен Тайе; Ие1ете р; Ие!еге р; невероятно, приведет к ошибке во время выполнения Глава 10. Классы 910 Конструктор ТаЫе:: ТаЫе() вызывается дважды, как и деструктор ТаЬ1е:: -ТаЫе () . К сожалению, операции иеп и Ые1еге не точно соответствуют друг другу в данном примере: объект, адресуемый указателем р, удаляется дважды, а объект, адресуемый указателем а — ни разу.
То, что объект не удаляется операцией Не1еге, не является ошибкой с формальной точки зрения языка С++; это лишь напрасный перерасход памяти (образно говорят об утечке памяти— тетогу !еа!г). Но для программы, предлагаемой к длительному непрерывному использованию, зто сильно вредит производительности и может трактоваться как откровенный брак в ее реализации. Существуют средства для помощи в нахождении ошибок, приводящих к утечкам памяти. Двойное освобождение памяти является серьезной операционной ошибкой с непредсказуемыми результатами, чаще всего катастрофическими. Некоторые реализации С++ могут предоставлять услуги автоматической сборки мусора, сканируя объекты в свободной памяти с целью нахождения неиспользуемых объектов. Поведение сборщиков мусора не стандартизовано.
При наличии автоматически работающих сборщиков мусора операция г1е1еге может спровоцировать ошибку двойного удаления объектов. Чаще всего это можно рассматривать как мелкое неудобство, ибо если известно, что работает сборщик мусора, деструкторы, освобождающие память, могут быть просто опущены. Конечно, все зто ведет к непереносимости программ, к некоторой потере производительности и, возможно, к неточно предсказуемому их поведению (5С.9.!). После того как выполнена операция Ие1еге, к объекту нельзя обращаться никоим образом. К сожалению, реализации не в состоянии надежно вьивлять такого рода логические ошибки на стадии компиляции. Пользователь может явным образом определять поведение операций иеп и г1е1еге (см.
З6.2.6.2 и 515.6). Также возможно определять варианты взаимодействия операций выделения памяти, конструирования (инициализации) и генерации/обработки исключений (см. 514.4.5 и в)9.4.5). Создание массивов в свободной памяти обсуждается в З10.4.7. 10.4.6. Классовые объекты как члены классов Рассмотрим класс, который содержит информацию о небольшой организации: с!аев С!иЬ ( вгг!ид пате; ТаЫе тетьегю ТаЫе о)У(сегв; Раге Тоипдед; У...
С!иЬ (сопв! вгг!ила и, Регель) )' Конструктор класса С1иЬ принимает в качестве аргументов имя клуба и дату его основания. Аргументы конструкторам членов класса С1иЬ передаются с помощью списка инициализации членов (тетьег!и!г!а!цег !а!) в определении конструктора объемлющего их класса: 10.4. Объекты СгиЬ:: С!иЬ (сопи( лппв и, РагеЯ) : пате (и ), тетЬегв ( ), о!У)сегв ( ), Тоипдед ф$) // ... Инициализаторы членов класса предваряютсл двоеточием и отделяются друг от друга запятыми.
Конструкторы членов класса вызываются до момента выполнения тела конструктора содержащего их класса. Порядок выполнения конструкторов членов соответствует порядку их объявления в определении объемлющего класса, а не порядку появления инициализаторов в списке инициализации. Во избежание путаницы, лучше упорядочить список инициализации в точном соответствии с порядком объявления членов.
Порядок вызова деструкторов членов обратный по отношению к вызову их конструкторов, и выполняются они после выполнения тела деструктора объемлющего класса. Если конструктор члена класса не требует аргументов, то в списке инициализации соответствующий ему инициалйзатор можно просто опустить: СгаЬ::С)иЬ(сопв! вгг!пда п, Рассад): пате(п), 1оипдед(1)1) ( // ... ) Данная версия конструктора полностью эквивалентна его предыдущей версии. В обеих версиях член ой)сета инициализируется умолчательным конструктором класса заЫе (для этого конструктора предусмотрено умолчательное значение аргумента, равное 15).
Когда уничтожается объект объемлющего класса (например, класса С1иЬ), сначала выполняется тело деструктора этого класса (если таковой определен), а затем выполняются деструкторы членов в порядке, обратном порядку вызова их конструкторов (то есть в порядке, обратном объявлению членов в объемлющем классе). Можно сказать, что конструктор собирает среду исполнения функций-членов в порядке снизу-вверх (сначала конструкторы членов, а затем конструктор объемлющего класса), в то время как деструктор демонтирует эту среду сверху вниз. 10.4.6.1.
Обязательная инициализация членов Инициализаторы членов необходимы (обязательны) в тех случаях, когда инициализация отличается от присваивания — для членов класса с типами, не имеющими умолчательных конструкторов, для константных членов и для ссылок. Например: с!пав Х ( сола! !и! Р С1иЬ с1! С1 Ы.с! // ... Х(оггй, соплво!пда и, Рагс д, С(иЬь с): !(!1),с1(п,д),гс(с) () З12 Глава 10. Классы Не существует никаких других способов инициализации таких членов, а отсутствие их инициализации является ошибкой. Однако для большинства иных типов существует выбор между применением списка инициализации и присваиваниями. В таких случаях я предпочитаю использовать список инициализации, делая тем самым факт инициализации особо наглядным.