|
Программирование >> Включение нужных заголовков
Что из этого следует? К счастью, ничего особенно сложного: если переносимость вас не интересует, если вы хотите изменить значение элемента в контейнере set/multiset и ваша реализация STL это разрешает - действуйте. Помните о том, что ключевая часть элемента (то есть часть элемента, определяющая порядок сортировки элементов в контейнере) должна сохраниться без изменений; если программа должна быть переносимой, элементы контейнеров set/ mul ti set модифицироваться не могут (по крайней мере, без преобразования const cast). Кстати, о преобразованиях. Вы убедились в том, что изменение вторичных данных элемента set/mul ti set может быть вполне оправданно, поэтому я склонен показать, как это делается - а точнее, делается правильно и переносимо. Сделать это нетрудно, но при этом приходится учитывать тонкость, о которой забывают многие программисты - преобразование должно приводить к ссылке. В качестве примера рассмотрим вызов setTitle, который, как было показано, не компилируется в некоторых реализациях: EmpIDSet::iterator i=se.find(selectedID): if (i!=se.end()) { i->setTitle( Corporate Deity ); Некоторые реализации STL } выдают ошибку в этой строке, поскольку *i имеет атрибут const Чтобы этот фрагмент нормально компилировался и работал, необходимо устранить константность *i. Правильный способ выглядит так: if (i!=se.end()){ Устранить const cast<Eniployee&>(*i).setTitle( Corporate Deity ): константность *i Мы берем объект, на который ссылается i, и сообщаем компилятору, что результат должен интерпретироваться как ссылка на (неконстантный) объект Employee, после чего вызываем setTitle для полученной ссылки. Я не буду тратить время на долгие объяснения и лучше покажу, почему альтернативное решение работает совсем не так, как можно было бы ожидать. Многие программисты пытаются воспользоваться следующим кодом: if (i!=se.end()){ Преобразовать *i static cast<Eniployee>(*i).setTitle( Corporate Deity ); к Employee Приведенный фрагмент эквивалентен следующему: if (i!=se.end()){ To же самое. ((Eniployee)(*i)).setTitle( Corporate Deity ); но с использованием } синтаксиса С Оба фрагмента компилируются, но вследствие эквивалентности работают неправильно. На стадии выполнения объект *i не модифицируется, поскольку в обоих случаях результатом преобразования является временный анонимный объект - копия *i, и setTitle вызывается для анонимного объекта, а не для *i! Обе синтаксические формы эквивалентны следующему фрагменту: if (i!=se.end()){ Employee tempCopy(*i): Скопировать *i в tempCopy tempCopy.setlitleC Corporate Deity ): Изменить tempCopy Становится понятно, почему преобразование должно приводить именно к ссылке - тем самым мы избегаем создания нового объекта. Вместо этого результат преобразования представляет собой ссылку на а/ществующий объект, на который указывает i. При вызове setTitle для объекта, обозначенного ссылкой, функция вызывается для *i, чего мы и добивались. Все сказанное хорошо подходит для контейнеров set и multiset, но при переходе к map/multimap ситуация усложняется. Вспомните, что map<K,V> и multimap<K,V> содержат элементы типа pair<con5 K.V>. Объявление const означает, что первый компонент пары определяется как константа, а из этого следует, что любые попытки устранить его константность приводят к непредсказуемому результату. Теоретически реализация STL может записывать такие данные в область памяти, доступную только для чтения (например, в страницу виртуальной памяти, которая после исходной записи защищается вызовом системной функции), и попытки устранить их константность в лзшем случае ни к чему не приведут. Я никогда не слышал о реализациях, которые бы поступали подобным образом, но если вы стремитесь придерживаться правил, установленных в Стандарте, - никогда не пытайтесь устранять константность ключей в контейнерах тар и multimap. Несомненно, вы слышали, что подобные преобразования рискованны. Надеюсь, вы будете избегать их по мере возможности. Выполняя преобразование, вы временно отказываетесь от страховки, обеспечиваемой системой типов, а описанные проблемы дают представление о том, что может произойти при ее отсутствии. Многие преобразования (включая только что рассмотренные) не являются абсолютно необходимыми. Самый безопасный и универсальный способ модификации элементов контейнера set, multiset, map или multimap состоит из пяти простых шагов. 1. Найдите элемент, который требуется изменить. Если вы не уверены в том, как сделать это оптимальным образом, обратитесь к рекомендациям по поводу поиска в совете 45. 2. Создайте копию изменяемого элемента. Помните, что для контейнеров тар/ multimap первый компонент копии не должен объявляться константным - ведь именно его мы и собираемся изменить! 3. Удалите элемент из контейнера. Обычно для этого используется функция erase (см. совет 9). 4. Измените копию и присвойте значение, которое должно находиться в контейнере. 5. Вставьте новое значение в контейнер. Если новый элемент в порядке сортировки контейнера находится в позиции удаленного элемента или в соседней позиции, воспользуйтесь рекомендательной формой insert, повышающей эффективность вставки от логарифмической до постоянной сложности. В качестве рекомендации обычно используется итератор, полученный на шаге 1. EmpIDSet::iterator i= Этап 1: поиск изменяемого элемента se.find(selectedlD): if (i!=se.end()) { Employee e(*i): Этап 2: копирование элемента se.erase(i++): Этап 3: удаление элемента. Увеличение итератора сохраняет его действительным (см. совет 9) e.setTitleCCorporate Deity ): Этап 4: модификация копии se.insert(i.e): Этап 5: вставка нового значения. Рекомендуемая позиция совпадает с позицией исходного элемента Итак, при изменении на месте элементов контейнеров set и multiset следует помнить, что за сохранение порядка сортировки отвечает программист. Совет 23. Рассмотрите возможность замены ассоциативных контейнеров сортированными векторами Многие программисты STL, столкнувшись с необходимостью структуры данных с быстрым поиском, немедленно выбирают стандартные ассоциативные контейнеры set, multiset, map и multimap. В этом выборе нет ничего плохого, но он не исчерпывает всех возможных вариантов. Если скорость поиска действительно важна, подумайте об использовании нестандартных хэшированных контейнеров (см. совет 25). При правильном выборе хэш-функций хэшированные контейнеры могут обеспечить поиск с постоянным временем (а при неправильном выборе хэш-функций или недостаточном размере таблиц быстродействие заметно снижается, но на практике это встречается относительно редко). Во многих слзаях предполагаемое постоянное время поиска превосходит гарантированное логарифмическое время, характерное для контейнеров set, map и их mul ti-аналогов. Даже если гарантированное логарифмическое время поиска вас устраивает, стандартные ассоциативные контейнеры не всегда являются лучшим выбором. Как ни странно, стандартные ассоциативные контейнеры по быстродействию нередко уступают банальному контейнеру vector. Чтобы эффективно использовать Ниже приведен знакомый пример с контейнером объектов Employee, реализованный безопасным и переносимым способом: EmplIDSet se: Контейнер set объектов Employee, упорядоченных по коду Employee selectedID: Объект работника с заданным кодом
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |