А. Александреску - Современное проектирование на C++ (1119444), страница 50
Текст из файла (страница 50)
Несмотря на то что класс ьосИ пдягоху напоминает интеллектуальный указатель, вокруг него существует еше одна оболочка — сам шаблонный класс ввагтатг. тевр1ате <с1ааа т> с1азз вваггртг ( ьосКзпдргоху<т> орегасог->О сопле; ( гетигп ьоск1пдргоху<т>(розптее ); ) рг1чате: т* розовее ; Напомним, что в разделе 7.3, посвященном механике оператора ->, уже указывалось, что компилягор может применять оператор -> к одному и тому же выражению несколько раз, пока не обнаружит простой указатель.
Теперь представьте себе слелуший вызов (считая, что а классе уН Идет определена функция позоветИ пд). Звагтртг<ИИдет> зр = ...; зр->оовоиетЫ пдО; 206 Часть Н. Компоненты Здесь кроется один нюанс: оператор -> класса 5вагтагг возвращает временный объект класа ьоск1пдагоху<т>. Компилятор продолжает применять оператор ->. Оператор -> объекта класса ьоск1пдагоху<т> возвращает объект, имеющий тип в1ддет".
Компилятор использует этот указатель на объект класса в1ддет для вызова функции оо5оветб1пд. Во время вызова этой функции объект класса ьос)<1пдагоху<т> продолжает существовать и владеть объектом, находящимся в полной безопасности.
После возврата управления иэ функции Оозовесь)пд временный объект класса ьос)с1пдагоху<т> уничтожается, и объект класса в1ддет освобождается. Автоматический захват объектов — - хорошая иллюстрация расслоения интеллектуальных указателей, которое можно осуществить, внеся соответствующие изменения в стратегию 5тогаде. 7. 13.2. Многопоточность нв уровне регистрации денных Иногда, кроме объектов, на которые они ссылаются, интеллектуальные указатели манипулируют дополнительными данными.
Как указывалось в разделе 7.5, несколько интеллектуальных указателей с подсчетом ссылок скрытно используют один н тот же счетчик. Если скопировать такой указатель из одного потока в другой, возникнут два интеллектуальных указателя, использующих один и тот же счетчик ссылок.
Разумеется, они оба продолжают указывать на тот же объект, но пользователю известно, какой из этих указателей в данный момент владеет им. В то же время счетчик ссылок пользователю не доступен и управляется исключительно интеллектуальным указателем. Для многопоточной среды опасность представляют не только интеллектуальные указатели с подсчетом ссылок.
Интеллектуальные указатели с отслеживанием ссылок (раздел 7.5.4) содержат указатели друг на друга, которые также относятся к совместно используемым данным. Связывание ссылок объединяет интеллектуальные указатели в группу, причем они не обязаны принадлежать одному и тому же потоку. Следовательно, каждый раз, когда выполняется копирование, присваивание или уничтожение интеллектуального указателя с отслеживанием ссылок, нужно решать вопрос о захвате объекта, в противном случае дважды связанный список может оказаться разрушенным. В заключение отметим, что вопросы, связанные с многопоточностью, в итоге влияют на реализацию интеллектуальных указателей.
В качестве примера рассмотрим, как многопоточность влияет на подчет ссылок и их связывание. 7.13.2.1. Многопоточный подсчет ссылок При копировании интеллектуальных указателей из разных потоков счетчик ссылок в разных потоках увеличивается непредсказуемым образом. Как показано в приложении, операция инкрементации не является атомарной. Для инкрементации и декрементации целых чисел в многопоточной среде нужно использовать тип т)эгеаНпдмоде)<т>::тпгтуре, а также функции Атов1стпсгевепт и дтов1соесгевепт.
Это все немного усложняет. Вернее, ситуация осложняется, если мы хотим сделать подсчет ссылок независимым от потоков. Следуя принципам разработки классов на основе стратегий, разложим класс на элементы, описывающие его поведение, и свяжем с каждым из них соответствующий шаблонный параметр.
В идеале класс 5вагтатг задавал бы стратегии овпегэмр и тбгеаФпдмоде1, применяя их лля корректной реализации. Однако при подсчете ссылок в многопоточной среде все намного запутаннее. Например, счетчик должен иметь тип т)эгеаФпдмобе)<т>::тпттуре. Следовательно, 2Р7 Глава7. Интеллектуальные указатели вместо использования операторов ++ и -- приходится применять функции Асов(сспсгевепс и Асов1соесгевепс. Потоки и счетчик ссылок сливаются в одно целое, их невероятно трулно разъединить. Лучше всего включить многопоточность в стратегию оппегзп(р. В этом случае можно получить лве реализации: яеРсоипс(пд и яеРсоцпсзпдмт.
7.13.2.2. Многопоточное связывание ссылок Рассмотрим деструктор интеллектуального указателя со связыванием ссылок. севр1асе <с1азз т> с1азз 5вагсясг ( риЫ зс: -5пагсясг() )Р (ргеч == пехс ) де1есе ро(псее ; ) е1зе ( ргеч ->пехт = пехс пехс ->ргеч = ргеч рг(часе: т* робпсее ; 5вагсясг* ргеч ; 5вагСРСг* пехС ; Деструктор выполняет классическое удаление элемента из дважды связанного списка.
Для упрошения и ускорения реализации список сделан кольцевым — последний узел ссылается на первый. Это позволяет не проверять, являются ли указатели ргеч и пехс нулевыми. В кольцевом списке, состояшем из одного элемента, указатели ргеч и пехс будут равны указателю сМз. Если несколько потоков уничтожают интеллектуальные указатели, связанные друг с другом, деструктор, очевидно, должен быть атомарным (т.е. его работу не могут прервать лругие потоки).
В противнол1 случае работу деструктора класса 5вагсясг сможет прервать любой поток. Например, это может произойти в момент между обновлением указателей ргеч и пехс . В этом случае поток, прервавший работу деструктора, будет оперировать разрушенным списком. Аналогичные рассуждения касаются и конструктора копирования, и оператора присваивания класса 5вагСРСг. Эти функции должны быть атомарными, поскольку они манипулируют со списком владения. Интересно, что здесь нельзя применить семантику захвата объектов.
В приложении, приведенном в конце книги, стратегии захвата разделены на стратегии уровня классов (с!азз )ече! зсгасед)ез) и стратегии уровня абьектав (о)хес1-)ече! ыпнерез). Операции захвата на уровне классов захватывают все объекты данного класса. В то же время операции захвата на уровне объектов относятся только к одному объекту. В первом случае тратится меньше памяти (только олин мьютекс на класс), но возникают проблемы с производительностью программы. Второй случай (один мьютекс на объект) сложнее, но позволяет создавать реализации, которые работают быстрее. 208 Часть й. Компоненты К интеллектуальным указателям нельзя применять стратегию захвата на уровне объектов, поскольку операция копирования манипулирует сразу тремя обьектами: текущий объект, подлежащий вставке или удалению, предыдущий обьект и следующий объект в списке владения.
Если все же возникает необходимость реализовать захват на уровне объектов, следует убедиться, что каждому объекту соответствует один мьютекс, поскольку для каждого объекта существует отдельный список владения. Мьютексы для каждого объекта можно разместить в динамической памяти, хотя это сводит к нулю все преимушества связывания ссылок над их подсчетом. Связывание ссылок привлекательно именно потому, что оно не использует динамическую память. В качестве альтернативы можно использовать стратегию внедрения: мьютекс хранится в объекте, на который ссылается интеллектуальный указатель. Однако существование разумной и эффективной альтернативы — интеллектуальных указателей с подсчетом ссылок — вынуждает отказаться от реализации этой стратегии.
Итак, интеллектуальные указатели, использующие подсчет ссылок или их связывание, возникают в многопоточной среде. Для реализации безопасной стратегии подсчета ссылок необходимы атомарные операции. Для реализации безопасной стратегии связываиия ссылок нужны мьютексы. Класс 5вагтятг реализует только безопасную стратегию подсчета ссылок.
7.14. Сборка До сих пор мы рассматривали каждый вопрос по отдельности. Настало время собрать все решении воедино и воплотить их в реализации класса 5вагтятг. Мы будем по-прежнему следовать принципам разработки классов на основе стратегий, описанным в главе 1. Каждый аспект проектирования, не имевший единственного решения, реализуется в классах стратегий.
Шаблонный класс 5иагтятг получает каждую стратегию н виде отдельного шаблонного параметра, наследует все эти шаблонные параметры, позволяя соответствующим стратегиям сохранять состояние. Вернемся мысленно к предыдущим разделам, перечисляя основные аспекты класса 5иагтртг, каждый из которых представляет собой отдельную стратегию.