|
Программирование >> Перегруженные имена функций и идентификаторы
Тонкости того, почему operator->() возвращает именно указатель A* (у которого есть свой селектор), а не, скажем, сс1ку A& и все равно все компилируется таким образом, что выполнение доходит до метода A::method(), пропустим за ненадобностью - здесь мы не планируем рассказывать о том, как работает данный механизм и какие приемы применяются при его использовании. Достоинства подобного подхода, в принципе, очевидны: возникает возможность контроля за доступом к объектам; малость тривиальных телодвижений и получается указатель, который сам считает количество используемых ссылок и при обнулении автоматически уничтожает свой объект, что позволяет не заботиться об этом самостоятельно... не важно? Почему же: самые трудно отлавливаемые ошибки - это ошибки в употреблении динамически выделенных объектов. Сплошь и рядом можно встретить попытку использования указателя на удаленный объект, двойное удаление объекта по одному и тому же адресу или неудаление объекта. При этом последняя ошибка, в принципе, самая невинная: программа, в которой не удаляются объекты (значит, теряется память, которая могла бы быть использована повторно) может вполне спокойно работать в течение некоторого периода (причем это время может спокойно колебаться от нескольких часов до нескольких дней), чего вполне хватает для решения некоторых задач. При этом заметить такую ошибку довольно просто: достаточно наблюдать динамику использования памяти программой; кроме того, имеются специальные средства для отслеживания подобных казусов, скажем, BoundsChecker. Первая ошибка в данном списке тоже, в принципе, довольно элементарная: использование после удаления скорее всего приведет к тому, что операционная система скажет соответственное системное сообщение. Хуже становится тогда, когда подобного сообщения не возникает (т.е., данные достаточно правдоподобны или область памяти уже занята чем-либо другим), тогда программа может повести себя каким угодно образом. Вторая ошибка может дать самое большое количество неприятностей. Все дело в том, что, хотя на первый взгляд она ничем особенным не отличается от первой, однако на практике вторичное удаление объекта приводит к тому, что менеджер кучи удаляет что-то совсем немыслимое. Вообще, что значит удаляет ? Это значит, что помечает память как пустую (готовую к использованию). Как правило, менеджер кучи, для того чтобы знать, сколько памяти удалить, в блок выделяемой памяти вставляет его размер. Так вот, если память уже была занята чем-то другим, то по неверному указателю находится неправильное значение размера блока, вследствие этого менеджер кучи удалит некоторый случайный размер используемой памяти. Это даст следующее: при следующих выделениях памяти (рано или поздно) менеджер кучи отдаст эту неиспользуемую память под другой запрос и... на одном клочке пространства будут ютиться два разных объекта. Крах программы произойдет почти обязательно, это лучшее что может произойти. Значительно хуже, если программа останется работать и будет выдавать правдоподобные результаты. Одна из самых оригинальных ошибок, с которой можно столкнуться и которая, скорее всего, будет вызвана именно повторным удалением одного и того же указателя, то, что программа, работающая несколько часов, рано или поздно падет в функции malloc(). Причем проработать она должна будет именно несколько часов, иначе эта ситуация не повторится. Таким образом, автоматическое удаление при гарантированном неиспользовании указателя, это очевидный плюс. В принципе, можно позавидовать программистам на Java, у которых аналогичных проблем не возникает; зато, у них возникают другие проблемы. Целесообразность использования умнгх указателей хорошо видно в примерах реального использования. Вот, к примеру, объявление умного указателя с подсчетом ссылок: template<class T> class MPtr public: MPtr(); MPtr(const MPtr<T>& p); ~MPtr(); MPtr(T* p); T* operator->() const; operator T*() const; MPtr<T>& operator=(const MPtr<T>& p); protected: struct RealPtr T* pointer; unsigned int count; RealPtr(T* p = 0); ~RealPtr(); RealPtr* pointer; private: Особенно стоит оговорить здесь конструктор MPtr::MPtr(T* p), который несколько выбивается из общей концепции. Все дело в том, что гарантировать отсутствие указателей на реальный объект может лишь создание такого объекта где-то внутри, это сделано в MPtr::MPtr(), где вызов new происходит самостоятельно. В итоге некоторая уверенность в том, что значение указателя никто нигде не сохранил без использования умного указателя, все-таки есть. Однако, очень нередко встречается такое, что у типа T может и не быть конструктора по умолчанию и объекту такого класса непременно при создании требуются какие-то аргументы для правильной инициализации. Совершенно правильным будет для подобного случая породить из MPtr новый класс, у которого будут такие же конструкторы, как и у требуемого класса. Оттого что подобный конструктор MPtr::MPtr(T* p) будет использоваться только лишь как MPtr<T> ptr(new T(a,b,c)) и никак иначе, этот конструктор введен в шаблон. Еще один спорный момент: присутствие оператора преобразования к T*. Его наличие дает потенциальную возможность где-нибудь сохранить значение реального указателя. Помимо MPtr можно использовать еще одну разновидность умных указателей, которая закономерно вытекает из описанной выше и отличается только лишь одной тонкостью: template<class T> class MCPtr { public:
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |