Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 [ 62 ] 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

Легко можно сделать так, что интеллектуальный указатель никогда не сможет стать нулевым (очень полезное свойство в практических приложениях). Это значит, что любой интеллектуальный указатель всегда корректен (если вы не манипулируете простыми указателями с помощью функции Getlmpl Ref). Реализовать это свойство интеллектуального указателя можно с помощью конструктора, генерирующего исключительную ситуацию при получении нулевого указателя.

template <c1ass Т> class SmartPtr {

public:

SmartPtrCT* p) : pointee Cp) {

if C!p) throw NullPointerExceptionC);

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

Разрещение или запрет нулевых значений влияют и на конструктор по умолчанию. Если интеллектуальный указатель не допускает нулевых значений, как конструктор по умолчанию может инициализировать обычный указатель? Применения конструктора по умолчанию можно избежать, но это значительно затрудняет создание интеллектуальных указателей. Например, что вы будете делать, если у вас есть переменная-член класса SmartPtr, но нет подходящей функции, инициализирующей ее во время создания объекта? В заключение отметим, что настройка инициализации содержит выбор подходящих значений, задаваемых по умолчанию.

7.10.2. Проверка перед разыменованием

Проверка перед разыменованием имеет больщое значение, поскольку разыменование нулевого указателя приводит к непредсказуемым последствиям. Во многих приложениях это соверщенно недопустимо, поэтому проверка корректности указателя перед его разыменованием обязательна. В классе SmartPtr она выполняется операторами -> и *.

В отличие от проверки во время инициализации, проверка перед разыменованием может снизить производительность вашего приложения, поскольку обычно разыменование интеллектуальных указателей вьшолняется намного чаще, чем их создание. Следовательно, следует стремиться поддерживать баланс между безопасностью и скоростью. Есть хорошее правило: начинать со строгой проверки всех указателей, а затем снимать проверку с некоторых интеллектуальных указателей по совету профилировщика.

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

7.10.3. Сообщения об ошибках

Единственный разумный способ регистрировать ошибки - генерировать исключительные ситуации.

Можно предпринять некоторые предосторожности, позволяющие избежать появления ошибок. Например, если указатель перед разыменованием оказался нулевым,



его можно проинициализировать на лету. Эта правильная и ценная стратегия называется ленивой инициализацией (lazy initialization) - объект создается только в тот момент, когда он впервые используется.

Если проверка должна выполняться только в процессе отладки программы, можно использовать стандартный макрос assert и подобные ему более сложные макросы. В окончательном варианте программы компилятор игнорирует проверки. Следовательно, если все ссылки на нулевые значения во время отладки уже удалены, во время выполнения профаммы проверку можно не повторять.

В классе SmartPtr все проверки сосредоточены в стратегии Cliecl<ing, реализующей соответствующие функции (которые при необходимости могут использовать ленивую инициализацию).

7.11. Интеллектуальные указатели на константные объекты и константные интеллектуальные указатели

Обычные указатели реализуют два вида работы с константами: указатели на константные объекты и собственно константные указатели. Эти свойства указателей иллюстрируются следующим фрагментом профаммы.

const Something* рс = new Something; Указывает на

константный объект pc->ConstMemberFunction(); правильно pc->NonConstMemberFunction(); неправильно delete рс; Как ни странно, правильно*

Something* const рс = new Something; Константный указатель

cp->NonConstMemberFunction(); Теперь правильно

ср = new Something; неправильно, константному указателю

ничего нельзя присваивать const Something* const срс =

new Something; константный указатель на константный объект cpc->ConstMemberFunctionO; правильно cpc->NonConstMemberFunction(); неправильно срс = new Something; неправильно, константному указателю

ничего нельзя присваивать

Соответственно, класс SmartPtr используется следующим образом.

Интеллектуальный указатель на константный объект

SmartPtr<const Something> spc(new something);

Константный интеллектуальный указатель

const SmartPtr<Something> scpCnew Something);

константный интеллектуальный указатель на константный объект

const SmartPtr<const Something> scpcCnew Something);

Шаблонный класс SmartPtr может обнаруживать, является ли объект, на которьй он ссылается, константным, с помощью частичной специализации или шаблонного класса TypeTraits, определенного в главе 2. Второй способ более предпочтителен, поскольку он не приводит к дублированию исходного кода, как при частичной специализации.

* Вопрос "Почему оператор delete можно применять к указателям на константные объекты?" всегда вызывает яростные споры в фуппе новостей comp.std.c++. Однако, хорошо это или плохо, язык допускает такую конструкцию.



Класс SmartPtr имитирует семантику указателей на константные объекты, константных указателей, а также их комбинации.

7.12. Массивы

в большинстве случаев вместо динамических массивов и операторов new[] и delete [] лучше использовать стандартный класс std::vector. Этот класс полностью обеспечивает возможности, предоставляемые динамическими массивами, а также многое другое, почти не тратя при этом дополнительных ресурсов.

Однако "в большинстве случаев" не означает "всегда". Сушествует много ситуаций, в которых полноценный вектор не нужен и даже не желателен. Именно здесь и нужны динамические массивы. В таких случаях без интеллектуальных указателей обойтись трудно. Между сложным шаблонным класом std::vector и динамическими массивами пролегает довольно глубокая пропасть. Интеллектуальные указатели могут выступать в роли мостиков через эту пропасть, предоставляя пользователю семантические возможности массивов.

С точки зрения интеллектуального указателя на массив единственным важным моментом является использование вызова оператора del ete [] pointee в его деструкторе вместо оператора delete pointee . Эта проблема уже решена в стратегии Ownership.

Второй момент связан с индексированным доступом к элементам массива с помощью оператора [], перегруженного для интеллектуальных указателей. Технически это вполне возможно. Фактически в предварительной версии класса SmartPtr для семантики массивов была предусмотрена отдельная стратегия. Однако интеллектуальные указатели очень редко ссылаются на массивы. В этих случаях индексированный доступ можно обеспечить с помощью функции Getlmpl.

Smartptr<widget> sp =

доступ к шестому элементу массива, на который ссылается интеллектуальный указатель sp Widget& obj = Getlmpl(sp)[5];

Было бы нерационально пытаться разработать дополнительные синтаксические конструкции за счет новой стратегии.

Класс SmartPtr поддерживает настраиваемое разрушение объектов с помощью стратегии Ownership. Следовательно, для удаления массивов можно использовать оператор delete[]. Однако класс SmartPtr не поддерживает арифметику указателей.

7.13. Интеллектуальные указатели и многопоточность

Чаще всего интеллектуальные указатели помогают совместно использовать объекты. В свою очередь, многопоточность тесно связана с совместным использованием объектов. Следовательно, многопоточность и интеллектуальные указатели связаны друг с другом.

Взаимодействие между ними проявляется на двух уровнях: на уровне объектов, на которые ссылаются интеллектуальные указатели (pointee object level), и на уровне регистрации данных (bookkeeping data level).

7.13.1. Многопоточность на уровне объектов

Если на один и тот же объект одновременно ссылаются интеллектуальный указатель и несколько потоков, желательно иметь возможность захватывать этот объект на время вызова функции-члена, выполненного с помощью оператора ->. Этого можно



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 [ 62 ] 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105