А. Александреску - Современное проектирование на C++ (1119444), страница 49
Текст из файла (страница 49)
теар1ате <с1азз т> с1аьа 5вагтятг ( рцЫзс: 5аагтвтг(т+ р) : робптее (р) зФ (!р) тпгоа мц11возптегдхсерт(опо; С, другой стороны, нулевое значение представляет собой удобное обозначение "неверного указателя" и также часто оказывается полезным. Разрешение или запрет нулевых значений влияют и на конструктор по умолчанию. Если интеллектуальный указатель не допускае~ нулевых значений, как конструктор по умолчанию может инициализировать обычный указатель? Применения конструктора по умолчанию можно избежать, но это значительно затрудняет создание интеллектуальных указателей.
Например, что вы будете делать, если у вас есть переменная-член класса 5вагтртг, но нет подходящей функции, инициализирующей ее во время создания объекта? В заключение отметим, что настройка инициализации содержит выбор подходящих значений, задаваемых по умолчанию. 7. 10.2. Проверка перед разыменованием Проверка перед разыменованием имеет большое значение, поскольку разыменование нулевого указателя приводит к непредсказуемым послелствиям. Во многих приложениях это совершенно недопустимо, поэтому проверка корректности указателя перед его разыменованием обязательна. В классе 5аагтятг она выполняется операторами -> и *. В отличие от проверки во время инициализации, проверка перед разыменованием может снизить производительность вашего приложения, поскольку обычно разыменование интеллектуальных указателей выполняется намного чаще, чем их создание.
Следовательно, следует стремиться поддерживать баланс между безопасностью и скоростью. Есть хорошее правило: начинать со строгой проверки всех указателей, а затем снимать проверку с некоторых интеллектуальных указателей по совету профилировщика. Существуют ли принципиальные различия между проверкой во время инициализации и проверкой перед разыменованием? Нет, так как они связаны л1ежду собой. Если во время инициализации выполняется строгая проверка, то проверка перел разыменованием становится излишней, поскольку указатели, прошедшие проверку во время инициализации, всегда корректны.
7. 10.3. Сообщения об ошибках Елинственный разумный способ регистрировать ошибки — генерировать исключительные ситуации. Можно предпринять некоторые предосторожности, позволяющие избежать появления ошибок. Например, если указатель перед разыменованием оказался нулевым, 203 Глава 7. Интеллектуальные указатели его можно проинициализировать на лету. Эта правильная и ценная стратегия называется ленивой инициализацией ((агу (п(г(а!)заз)оп) — объект создается только в тот момент, когда он впервые используется. Если проверка должна выполняться только в процессе отладки программы, можно использовать стандартный макрос аввегт и подобные ему более сложные макросы.
В окончательном варианте программы компилятор игнорирует проверки. Следовательно, если все ссылки на нулевые значения во время отладки уже удалены, 'во время выполнения программы проверку можно не повторять, В классе 5юагтятг все ПроВерки сосредотоЧеНЫ в СтратегИИ СЬесМ(пд, реализуюшей соответствуюшие функции (которые при необходимости могут исподьзовать ленивую инициализацию). 7.11. Интеллектуальные указатели на константные обьекты и константные интеллектуальные указатели Обычные указатели реализуют два вида работы с константами: указатели на константные объекты и собственно константные указатели, Эти свойства указателей иллюстрируются следуюшим фрагментом программы. сопят 5оюетыпд* рс пев 5отетыпд; // указывает иа // константный обьект рс->СопзтмеюЬегкипстбопО; // правильно рс->нопсопзтмеюЬегкипстбопо; // неправильно де1ете рс; // как ни странно, правнльно4 5оюетЫпд< сопят рс = пев 5оюетЫпд; // константный указатель ср->нопСопзтмеюЬегяцпстбопо „ '// теперь правильно ср = пен 5оюетЬ(пд; // неправильно, константному указателю // ничего нельзя присваивать сопят 5оюетЫпд" сопят срс = пев 5оюетЫпд; // константный указатель на константный объект срс->сопзтмеюЬегвцпст!опт; //правильно срс->нопсопзтмеюЬегкипст!опО; // неправильно срс = пев 5оюетЫпд; // неправильно, константному указателю // ничего нельзя присваивать Соответственно, класс 5юагсвтг используется следуюшим образом.
// интеллектуальный указатель на константный объект 5юагтятг<сопзт 5оюесЬ)пд> зрс(пев 5оюетЫ пд); // константный интеллектуальный указатель сопят 5юагтятг<5оюетЬ!пд> зср(пев 5оюетЫпд); // константный интеллектуальный указатель на константный объект сопят 5юагтятг<сопзт 5оюетЫпд> зсрс(пев 5оюетЫпд); Шаблонный класс 5юагтятг может обнаруживать, является ли объект, на который он ссылается, константным, с помощью частичной специализации или шаблонного класса туретга)тз, определенного в главе 2. Второй способ более предпочтителен, поскольку он не приводит к дублированию исходного кода, как при частичной специализации.
< Вопрос вПочему оператор де) ете можно применять к указателям на константные объекты"." всегда вызывает яростные споры в группе новостей сошра!о.с++. Однако, хорошо зто или плохо, язык допускает такую конструкцию. 204 Часть (!. Компоненты Класс 5вагсРсг имитирует семантику указателей на константные объекты, константных указателей, а также их комбинации. 7.12. Массивы В большинстве случаев вместо динамических массивов и операторов пав[3 и де- 1есеЦ лучше использовать стандартный класс зсд::чессог.
Этот класс полностью обеспечивает возможности, предоставляемые динамическими массивами, а также многое другое, почти не тратя при этом дополнительных ресурсов. Однако "в большинстве случаев" не означает "всегда". Существует много ситуаций, в которых полноценный вектор не нужен и даже не желателен. Именно здесь и нужны динамические массивы.
В таких случаях без интеллектуальных указателей обойтись трудно. Между сложным шаблонным класом зсд::чессог и динамическими массивами пролегает довольно глубокая пропасть. Интеллектуальные указатели могут выступать в роли мостиков через эту пропасть, предоставляя пользователю семантические возможности массивов. С точки зрения интеллектуального указателя на массив единственным важным моментом является использование вызова оператора де1есе[3 ро1псее в его деструкторе вместо оператора де1есе роспсее .
Эта проблема уже решена в стратегии ОвпегзЫ р. Второй момент связан с индексированным доступом к элементам массива с помощью оператора [3, перегруженного для интеллектуальных указателей. Технически это вполне возможно. Фактически в предварительной версии класса 5вагсРсг для семантики массивов была предусмотрена отдельная стратегия. Однако интеллектуальные указатели очень редко ссылаются на массивы. В этих случаях индексированный доступ можно обеспечить с помощью функции сеттер). 5вагСРСг<Ш!ддес> зр = О доступ к шестому элементу массива, на который ссылается О интеллектуальный указатель зр шзддесв оь) = Оествр1(зр)[53; Было бы нерационально пытаться разработать дополнительные синтаксические конструкции за счет новой стратегии. Класс 5вагСРСг поддерживает настраиваемое разрушение объектов с помощью стратегии ОшпегзЬ)р.
Следовательно, для удаления массивов можно использовать оператор де1есе[3 Однако класс 5вагсРсг не поддерживает арифметику указателей. 7.13. Интеллектуальные указатели и многопоточность Чаше всего интеллектуальные указатели помогают совместно использовать объекты. В свою очередь, многопоточность тесно связана с совместным использованием объектов. Следовательно, многопоточность и интеллектуальные указатели связаны друг с другом.
Взаимодействие между ними проявляется на двух уровнях: на уровне объектов, на которые ссылаются интеллектуальные указатели (ро1пгее оЬ)есс (ече!), и на уровне регистрации данных (Ьооккеер(пй дага )ече!). 7. 13. 1. Многопоточность нв уровне объектов Если на один и тот же объект одновременно ссылаются интеллектуальный указатель и несколько потоков, желательно иметь возможность захватывать этот объект на время вызова функции-члена, выполненного с помощью оператора ->. Этого можно 205 Глава 7.
Интеллектуальные указатели лостичь, если интеллектуальный указатель будет возвращать объект-заместитель (ргоху оь)ес1), а не простой указатель. конструктор объекта-заместителя захватывает объект, на который ссылается интеллектуальный указатель, а его деструктор освобождает его.
Этот способ программирования описан в книге Страуструпа (Ягоцзсгир, 2000). Ниже приводится код, иллюстрирующий этот подход, Во-первых, рассмотрим класс и1ддет, имеющий лве конструкции для захвата обьекта: функции-члены ьос1с и цп1осй. После вызова функции ьосй открывается безопасный доступ к объекту, при этом вызов такой функции остальными потоками блокируется. После вызова функции цп1осМ объект может поступать в распоряжение остальных потоков. с1азз наедет ( чей ьосМО' чо( 6 цп1 о ей О Затем определим шаблонный класс ьосК1пдргоху. Он предназначен лля захвата объекта (с помощью описанного выше механизма ьоск/цп1осМ) на время существования обьекта класса ьосй(пдргоху. теир1ате <с1аьв т> с1аьз ьосМзпдягоху риЬ11с: ~.оскзпдргоху(т» роЬ3) : розптее (роЬ3) ( ро(псее ->ьосМО; ЕосИпдРгахуО; ( ро(птее ->цп1оскО; ) т* орегатог->О сопзт ( гетцгп ро(атее ; ) рг(часе: ~осх1пдргохуб орегатог=(сопле ьосМпдргохуб); т* ро(птее; )1 Кроме конструктора и деструктора в классе ьосМпдргоху определен оператор ->, возвращающий указатель на объект.