Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 66
Текст из файла (страница 66)
Она опирается на тот факт, что статические объекты без конструкторов инициализируются нулевыми значениями. Большие сложности начинаются тогда, когда первый вызов критичен по времени выполнения, и дополнительные накладные расходы на проверку и возможную инициализацию становятся обременительными. В таких случаях помогуг дополнительные трюки (521.5.2). Альтернативным подходом в случае простых объектов является их представление в виде функций (й9.4.1): З1В Глава 10, Классы то конструирование нужно выполнять аккуратно и поэтапно. Еще стоит отметить, что нет аналогичной простой конструкции с флагом последнего использования (см. 59.4.1.! и 52!.5.2).
10.4.10. Временные обьекты Временные объекты (гетрогагу обуесгз) чаще всего создаются в процессе вычисления арифметических выражений. Например, при вычислении выражения к*у+а надо где-то сохранить промежуточный результат умножения х*у. За исключением случаев, когда эффективность выполнения кода особо критична (911.6), временные объекты не беспокоят программиста. Однако ж, иногда на них приходится обращать внимание (в!!.6, в22.4.7). Если временный объект не связан ссылкой и не используется для инициализации именованного объекта, он обычно уничтожается по достижении конца полного выражения, в рамках оценки которого он был создан. Полное выражение (1иВ ехргезз!он) — это выражение, которое не является подвыражением (частью) другого выражения.
Стандартный класс з(г(н» имеет функцию-член с згг(), которая возвращает массив символов в С-стиле, то есть с терминальным нулем (В3.5.1, В20.4.!). В нем также определена операция», означающая конкатенацию строк. Все это важные и полезные черты класса з(ганя. Однако их совместное использование порождает малоизвестную проблему. Вот пример: го(о' /(згпнда з1, зн(иль з2, згпндь зЗ) ( сонм сваг* сз = (з1»з2) .с Фг(); сонг«сзг (Г( згг!ен (сз= (з2»зЗ) .
с зо () ) <в за сз (О) == ' о' ) ( ~У сз иснользуется здесь ) ) Вероятно вашей первой реакцией будет «не надо так писать», и я абсолютно согласен с вашей реакцией. Но все же стоит рассмотреть этот пример подробнее. Создается временный объект класса зй"!ня для хранения результата конкатенации з1»з2. Потом указатель на С-строку извлекается из этого объекта. Затем— в конце выражения — временный объект уничтожается. Теперь зададимся вопросом, где же хранилась возвращаемая с помощью с зге О строка С-стиля? Она, вероятно, хранилась как часть временного объекта, содержащего результат конкатенации з1+з2. Но этот объект уничтожен, так что сз указывает на освобожденную память с непредсказуемым содержимым.
Возможно, операция соиг«сз и сработает как надо, но это будет в любом случае чистой удачей. Компилятор может обнаруживать и предупреждать о большинстве подобного рода проблем. Пример с оператором (г"тоньше. Само условие отработает как надо, ибо полным выражением, содержащим временный объект, хранящий результат конкатенации з2+зЗ, является само условие. Однако после оценки условия эта временная переменная будет уничтожена, так что дальнейшее применение сз под вопросом. 319 10 4. Объекты Причина, по которой в рассмотренном примере (и во многих других подобных примерах) возникла проблема с временной переменной, заключается в том, что высокоуровневый тип данных используется в несвойственном ему низкоуровневом стиле.
Более адекватный стиль программирования не только позволяет избежать рассмотренных проблем с временными переменными, но и порождает существенно более понятный код. Например: юЫг(я<пила л1, ззппла зг, зге(пла зЗ) ( Г«л1 -зг; лгг)пВ з = зг - Зг К(л . 1епВ( Л ( ) к В а а з ( 0] = = ' а ' ) ( //з используется здесь ) Временные объекты можно использовать в качестве инициализаторов для константных ссылок или именованных обьектов. Например: юЫ В (сопят з(пила, сопле зМпла ); юЫ й (згг(пль з1, шпиль лг) ( г лгг/пла л = Ы+ г; зге1пВ 55 = з1«зг; В (3, зз); // здесь можно использоеать л и зз ) Здесь все корректно.
Временный объект уничтожается только тогда, когда «его» ссылка или именованный объект выходят из области видимости. Помните, что возврат ссылки на локальную переменную является ошибкой (97.3), и что временный объект не может связываться с неконстантной ссылкой (95.5). Временный объект можно создавать прямым вызовом конструктора. Например: юЫ1(Ячареа з, тгх, (пгу) ( л. тосе (Ро(пг (х,у) ); // создается Ропп для передачи $ларе:;тоьеВ // ... ) Такие временные объекты уничтожаются точно так же, как и временные объекты, создаваемые неявно. 10.4.11.
Размещение обьектов в заданных блоках памяти С помощью операции пек«объекты по умолчанию создаются в свободной памяти. А что, если мы захотим разместить объект где-либо в другом месте? Рассмотрим простой класс: с1азз Х ( риЫ(с: Глава 10, Классы Х (йзз); У... )' Мы можем разместить динамически создаваемый объект где-угодно, если определим функцию выделения памяти орегагог лез«О с дополнительным (вторым) формальным параметром, фактическое значение которого будет задаваться при помощи дополнительного параметра в операции пеке юЫ* орегагог иезг(в(ее г, юЫ* р) (ге(ига р; ) ~У ем.
Я!945) юЫ* Ьит"= геизгегргег савз<юЫ*> (ОхгООг);,У некоторый значимый адрес Х* р2=иезг (Ьиг) Х; зу вызывается функция прего(ог пе»г Гзгсео~70, ЬиЯ й и конструируется об»ект Х в буфере Ьи1 Из-за такого применения вариант операции лен в виде пеп (ЬиД Х, предназначенный для передачи дополнительного аргумента функции орегагог пеп(), называется операцией «размещающее иеи» (р(асетеигиезв). Обратите внимание на то, что первым аргументом функции орегагог пеп() всегда является размер выделяемого блока памяти, и что размер размещаемого обьекта передается неявно (5) 5.6). Выбор конкретного варианта функции орегагог пои () для соответствия применяемому варианту операции пезг выполняется, как всегда, по правилу соответствия аргументор (57.4); при этом первый аргумент каждого варианта функции орегагог пеп() должен иметь тип в1хе 1.
Приведенный пример функции орегагог пе)г() является простейшим примером распределителя памяти для операции «размешаюшее пею». Он определен в стандартном заголовочном файле <пезг>. В рассмотренном примере мы использовали операцию приведения гезпгезргег саве, наиболее «безобразную на вид» и опасную из всех операций приведения (56.2.7). В большинстве случаев эта операция оставляет последовательность бит операнда нетронутой, изменяя лишь трактовку его типа. Ее применяют, например, в потенциально опасных, но иногда необходимых низкоуровневых системнозависимых преобразованиях целочисленных значений в указатели и наоборот. Операцию «размешаюшее пеи > можно использовать для помещения объектов в конкретные участки памяти: с1авв А гена ( риЫ1с: Ыггиа1 юЫ* а11ос (Ыге 1) =О; Ыггиа1 гоИ 1гее(гоЫ*) =О; з)' ...
)' юЫ* орегагог иезг (Все 1 ее, Агеиа* а) гегиги а->алое (ве) з Теперь объекты произвольных типов можно при необходимости размешать в тех или иных специфических участках памяти («аренах»). Например: ) 0.4. Объекты 321 ехгегп Агепа* Регттаееаг ехгегп Агепа* Дгагед; ивЫ е (ип г) ( Х* р = пег« (Регмыепн Х(г); Х* 9 = пеги (ойагед) Х (!) г г7.. ) УХ в ирене Регзгвгепг гу Х в арене Яагед Помещение объекта в область памяти, не находящуюся под управлением стандартного механизма вьиеления/освобождения свободной памяти, подразумевает специальные действия при уничтожении объекта.
Базовым механизмом является в этом случае явный вызов деструктора: гиЫдезггиу(Х* р, Агева* а) р->-Х(); а->угее (р) г ) У вызов деструктора (г освобождение памяти 10.4.12. Объединения Именованное объединение определяется как структура, в которой всем полям отводится одна и та же область памяти (8С.8.2). Объединение может иметь функции-члены, но не статические члены'. В общем случае, компилятор не может знать, какой член объединения используется; то есть тип объекта, хранящегося в объединении, неизвестен. Следовательно, объединение не может содержать члены, классы которых имеют явно определенные конструктор или деструктор. Иначе не будет возможности предотвратить хранящийся в объединении объект от непреднамеренной порчи, и невозможно будет вызвать правильный деструктор при выходе объединения из области видимости.
Подробнее этот вопрос рассматривается в книге Н.Н. Мартынов, «Программирование для %(пдог«а на С/Сз-г», том 2, М., Бином, 2006, стр. 67-69. — Прим. ред. Отметим, что явный вызов деструкторов и применение специальных вариантов глобильньи аллокаторов памяти в обычных случаях нецелесообразны. Все же, в определенных случаях они нужны. Например, трудно было бы реализовать обобщенный контейнер в духе стандартного библиотечного класса иесгиг (83.7.1, 916.3.8) без применения явного вызова деструктора. Новичкам лучше трижды подумать перед тем, как явно вызвать деструктор, а еше лучше проконсультироваться с опытным специалистом.
В 914.4.4 рассмотрено, как исключения взаимодействуют с операциями «размещающее пеим. Не существует отдельного синтаксиса лля размещения массивов. В этом нет нужды, так как операция «размещающее пен г и так работает с произвольными типами данных. Тем не менее, для массивов можно определить специальный вариант фуНКцИИ ирегашг де!еее () (819.4.5). Глава 10.
Классы Лучше всего ограничить использование объединений низкоуровневым кодом, или в виде части класса, содержащего информацию о том, что на самом деле хранится в объединении (см. 810.6[201). 10.5. Советы 1. 3. 4. 5. 6. 7. 8. 10. 11. 12. 13. 14. Представляйте концепции в виде классов; 810.!. Используйте открытые данные (структуры) только тогда, когда это на самом деле лишь данные, и для них не существует разумных инвариантов; 810.2.8.
Конкретные типы являются простейшими видами классов. Где только воз- можно, предпочитайте конкретные типы более сложным видам классов или открытым структурам данных; 810.3. Определяйте функцию в качестве функции-члена класса только в тех случа- ях, когда ей нужен непосредственный доступ к его внутренней структуре; 810.3.2.
Используйте пространства имен для явного выражения связи класса и его функций поддержки; 810.3.2. Объявляйте функции-члены коястанглнымц, если они не модифицируют со- стояния (значения) объектов; 810.2.6. Объявляйте функции-члены статическими в тех случаях, когда им нужен доступ к представлению класса, но не нужен вызов для конкретных объектов класса; 810.2.4. Используйте конструктор для установления инварианта класса; 810.3.1. Если конструктор класса выделяет ресурсы, то для освобождения ресурсов классу требуется деструктор; 810.4.1.