Анимация
JavaScript
|
Главная Библионтека Интеллектуальные указатели Интеллектуальным указателям посвящены миллионы строк кода и сотни книг. Благодаря тому, что в интеллектуальных указателях переплелись многие синтаксические и семантические проблемы, они стали одной из самых популярных, интересных и мощных идиом языка С++. В этой главе обсуждаются все аспекты интеллектуальных указателей (от простейших до самых сложных), а также наиболее распространенные ощибки, соверщаемые профаммистами при их реализации. Интеллектуальные указатели - это объекты языка С++, имитирующие обычные указатели с помощью реализации оператора -> и унарного оператора *. Кроме того, они часто скрытно выполняют полезные задания (например, осуществляют управление памятью или блокировку), освобождая приложение от детального конфоля за объектами, на которые они ссылаются. В главе не только обсуждаются интеллектуальные указатели, но и реализован шаблонный класс SmartPtr. Этот класс разработан на основе стратегий (глава 1). Он безопасен, эффективен и легок в использовании. Здесь рассмофены следующие вопросы. • Преимущества и недостатки интеллектуальных указателей. • Стратегии управления владением. • Неявные преобразования. • Проверки и сравнения. • Многопоточность. В главе описан обобщенный шаблонный класс SmartPtr. В каждом разделе рассматривается отдельная проблема, связанная с реализацией этого класса, а полная сборка класса выполняется в конце главы. Профаммисту мало знать, как устроен шаблонный класс SmartPtr, нужно еще уметь использовать, изменять и расширять его в своих профаммах. 7.1. Сто первое описание интеллектуальных указателей Что такое интеллектуальный указатель? Это класс, имитирующий синтаксис и семантику обычного указателя и выполняющий много другой полезной работы. Поскольку интеллектуальные указатели, ссылающиеся на объекты разных типов, имеют много общего, они почти всегда реализуются в виде щаблонов. template <c1ass т> class SmartPtr publi с: explicit SmartPtr(T* pointee) : pointee (pointee); SmartPtr& operator=(const SmartPtr& other); -SmartPtrO ; T& operator*() const return *pointee ; T* operator->() const { return pointee ; private: T* pointee ; Шаблон SmartPtr<T> хранит указатель на тип т в переменной-члене pointee . Именно так поступает большинство интеллектуальных указателей. В некоторых случаях в этих классах предусматривается некоторая дополнительная обработка данных, а сам указатель вычисляется на лету. Синтаксис и семантика обычных указателей имитируются следующими операторами шаблонного класса SmartPtr. class widget { public: void Fun(); SmartPtr<widget> sp(new Widget); sp->Fun(); (*sp).Fun(); Помимо определения класса sp, класс SmartPtr ничем не отличается от обычного указателя. В этом и заключается существо интеллектуального указателя: обычные указатели можно заменить интеллектуальными, не внося коренных изменений в профамму. Таким образом, можно легко получить дополнительные преимущества. Возможность практически не изменять код при использовании интеллектуальных указателей в больших приложениях - очень привлекательное и важное свойство. Однако, как мы увидим ниже, не все так просто. 7.2. Особенности интеллектуальных указателей Может возникнуть вопрос: "Зачем нужны интеллектуальные указатели?". Какую выгоду можно извлечь, заменив обычные указатели интеллектуальными? Все очень просто. Интеллектуальные указатели имеют семантику значений, а простые указатели - нет. Объект, имеющий семантику значений, - это объект, который можно копировать и присваивать. Числа типа i nt - прекрасный пример. Их можно свободно создавать, копировать и изменять. Указатель, который применяется для перемещения по буферу, также имеет семантику значений - он инишализируется адресом первой ячейки буфера и перемещается по нему вперед, пока не достигнет конца. По пути значения этого указателя можно копировать в другие переменные, сохраняя промежуточные результаты. Однако работать с указателями на объекты, созданные с помощью оператора new, нужно совсем иначе. Допустим, мы написали следующее выражение, widget* р = new widget; В этом случае переменная р не только указывает на объект класса Widget, размещенный в памяти, но и владеет им. Это значит, что при выполнении оператора delete р соответствующий объект класса widget будет уничтожен, а выделенная для него память освобождена. Таким образом, приведенный ниже код окажется неверным, widget* р = new widget; р = 0; присваиваем указателю р нечто иное Это происходит потому, что мы теряем владение объектом, на который ранее ссылался указатель р, и не имеем возможности вернуть его. Это приводит к утечке ресурсов. Более того, при копировании указателя р в другую переменную компилятор не передает ей автоматически владение участком памяти, на который ссылается указатель. Вместо этого в профамме появляются два указателя, содержащих адрес одного и того же объекта. Это нежелательно, поскольку может привести к повторному удалению уже удаленного объекта (со всеми вытекающими катасфофическими последствиями). Следовательно, указатели на динамически размещаемые объекты не должны иметь семантики значений - их нельзя копировать и присваивать как попало. Вот здесь-то и нужны интеллектуальные указатели. Больщинство интеллектуальных указателей, помимо имитации простых указателей, управляют владением объектами, на которые они ссылаются. Они распознают изменения в правах владения, а их десфукторы освобождают память, следуя тщательно продуманной сфатегий. Во многих интеллектуальных указателях содержится достаточно информации, чтобы правильно освободить память, занятую объектом, на который они ссылаются. Управление владением осуществляется разными способами в зависимости от решаемой задачи. Некоторые интеллектуальные указатели автоматически передают права на владение объектом; после копирования исходному указателю присваивается нулевой адрес, а результирующему - адрес объекта и права на владение. Именно такая Сфатегия реализована в стандартном интеллектуальном указателе std:: auto ptr. Другие интеллектуальные указатели используют подсчет ссылок: они отслеживают общее количество интеллектуальных указателей, ссылающихся на один и тот же объект, и, когда счетчик обнуляется, удаляют его. В заключение заметим, что существуют интеллектуальные указатели, при копировании которых создается дубликат объекта. Владение объектом - важное свойство интеллектуальных указателей. Управляя правами владения, интеллектуальные указатели гарантируют целостность и полноценную семантику значений. Поскольку права владения связаны в основном с созданием, копированием и уничтожением интеллектуальных указателей, соответствующие функции-члены наиболее важны. В следующих разделах мы рассмофИМ разные аспекты разработки и реализации интеллектуальных указателей. Нашей целью будет максимально полная имитация простых указателей, но не будем забывать, что интеллектуальный указатель - более широкое понятие. Между интеллектуальными и обычными указателями существует тонкая фань, отделяющая полный конфоль от хаоса. Мы увидим, что добавление на первый взгляд полезных свойств иногда оказывается весьма опасным. При реализации хороших интеллектуальных указателей следует соблюдать точный баланс. 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 |