Как работает неявное совместное использование данных
Неявное совместное использование данных работает автоматически и незаметно для пользователя, поэтому нам не надо в программном коде предусматривать специальные операторы для обеспечения этой оптимизации. Но поскольку хочется знать, как это работает, мы рассмотрим пример и увидим, что скрывается от нашего внимания. В этом примере используются строки типа QString — одного из многих неявно совместно используемых Qt—классов:
QString str1 = "Humpty";
QString str2 = str1;
Мы присваиваем переменной str1 значение «Humpty» (Humpty-Dumpty — Шалтай—Болтай) и переменную str2 приравниваем к переменной str1. К этому моменту оба объекта QString ссылаются на одну и ту же внутреннюю структуру данных в памяти. Кроме символьных данных эта структура данных имеет счетчик ссылок, показывающий, сколько строк QString ссылается на одну структуру данных. Поскольку обе переменные ссылаются на одни данные, счетчик ссылок будет иметь значение 2.
str2[0] = 'D';
Когда мы модифицируем переменную str2, выполняется действительное копирование данных, чтобы переменные str1 и str2 ссылались на разные структуры данных и их изменение приводило к изменению их собственных копий данных. Счетчик ссылок данных переменной str1 («Humpty») принимает значение 1, и счетчик ссылок данных переменной str2 («Dumpty») тоже принимает значение 1. Значение 1 счетчика ссылок означает, что данные не используются совместно.
str2.truncate(4);
Если мы снова модифицируем переменную str2, никакого копирования не будет происходить, поскольку счетчик ссылок данных переменной str2 имеет значение 1. Функция truncate() непосредственно обрабатывает значение переменной str2, возвращая в результате строку «Dump». Счетчик ссылок по-прежнему имеет значение 1.
str1 = str2;
Когда мы присваиваем строку str2 строке str1, счетчик ссылок для данных str1 снижается до 0 и приводит к тому, что теперь никакая строка типа QString не содержит значения «Humpty». Память освобождается. Обе строки QStrings теперь ссылаются на значение «Dump», счетчик ссылок которого теперь имеет значение 2.
Часто не пользуются возможностью совместного использования данных в многопоточных программах из-за условий гонок при доступе к счетчикам ссылок. В Qt этой проблемы не возникает. Классы—контейнеры используют инструкции ассемблера при реализации атомарных операций со счетчиками. Эта технология доступна пользователям Qt через применение классов QSharedData и QSharedDataPointer.
Ассоциативные контейнеры
Ассоциативный контейнер содержит произвольное количество элементов одинакового типа, индексируемых некоторым ключом. Qt содержит два основных класса ассоциативных контейнеров: QМар<К, T> и QHash.
QMap — это структура данных, которая содержит пары ключ—значение, упорядоченные по возрастанию ключей. Такая организация данных обеспечивает хорошую производительность операций поиска и вставки, а также при проходе данных в порядке их сортировки. Внутренне QMap реализуется как слоеный список (skip—list).
Рис. 11.6. Ассоциативный массив, связывающий QString с int.
Простой способ вставки элементов в ассоциативный массив состоит в использовании функции insert():
QMap map;
map.insert("eins", 1);
map.insert("sieben", 7);
map.insert("dreiundzwanzig", 23);
Можно поступить по-другому — просто присвоить значение заданному ключу:
map["eins"] = 1;
map["sieben"] = 7;
map["dreiundzwanzig"] = 23;
Оператор [ ] может использоваться как для вставки, так и для поиска. Но если этот оператор используется для поиска значения, для которого не существует ключа, будет создан новый элемент с данным ключом и пустым значением. Чтобы не создавать случайно пустые элементы, вместо оператора [ ] можно использовать функцию value():
int val = map.value("dreiundzwanzig");
Если ключ отсутствует в ассоциативном массиве, возвращается значение по умолчанию, создаваемое стандартным конструктором данного типа значений. Для базовых типов и указателей возвращается нуль. Мы можем определить другое значение, используемое по умолчанию, с помощью второго аргумента функции value(), например:
int seconds = map.value("delay", 30);
Это эквивалентно следующим операторам:
int seconds = 30;
if (map.contains("delay"))
seconds = map.value("delay");