8.8. Присвоение каждому экземпляру класса уникального идентификатора
8.8. Присвоение каждому экземпляру класса уникального идентификатора
Проблема
Требуется, чтобы каждый объект класса имел уникальный идентификатор.
Решение
Для отслеживания следующего доступного для использования идентификатора используйте статическую переменную-член. В конструкторе присвойте текущему объекту очередное доступное значение, а затем инкрементируйте статическую переменную. Чтобы понять, как это работает, посмотрите на пример 8.8.
Пример 8.8. Присвоение уникальных идентификаторов
#include <iostream>
class UniqueID {
protected:
static int nextID;
public:
int id;
UniqueID();
UniqueID(const UniqueID& orig);
UniqueID& operator=(const UniqueID& orig);
};
int UniqueID::nextID = 0;
UniqueID::UniqueID() {
id = ++nextID;
}
UniqueID::UniqueID(const UniqueID& orig) {
id = orig.id;
}
UniqueID& UniqueID::operator=(const UniqueID& orig) {
id = orig.id;
return(*this);
}
int main() {
UniqueID a;
std::cout << a.id << std::endl;
UniqueID b;
std::cout << b.id << std::endl;
UniqueID c;
std::cout << c.id << std::endl;
}
Обсуждение
Для отслеживания следующего доступного для использования идентификатора используйте статическую переменную. В примере 8.8 используется static int, но вместо нее можно использовать все, что угодно, при условии, что имеется функция, которая может генерировать уникальные значения.
В данном случае идентификаторы не используются повторно до тех пор, пока не будет достигнуто максимально возможное для целого числа значение. При удалении объекта его уникальное значение пропадает либо до перезапуска программы, либо до переполнения значения идентификатора. Эта уникальность в программе может иметь несколько интересных преимуществ. Например, при работе с библиотекой управления памятью, которая перемещает блоки памяти и обновляет значения указателей, можно быть уверенным, что для каждого объекта будет сохранено его первоначальное уникальное значение. При использовании уникальных значений в сочетании с рецептом 8.4, но применении map вместо list можно легко найти объект с заданным уникальным номером. Чтобы сделать это, просто отобразите уникальные ID на экземпляры объектов, как здесь.
static map<int, MyClass*> instmap;
Таким образом любой код, который отслеживает идентификаторы объектов, всегда сможет найти его без необходимости хранить ссылку на него.
Но это еще не все. Рассмотрим случай, когда один из этих объектов требуется добавить в стандартный контейнер (vector, list, set и т.п.). Стандартные контейнеры хранят копии объектов, добавляемых в них, а не ссылки или указатели на эти объекты (конечно, при условии, что это не контейнер указателей). Таким образом, стандартные контейнеры ожидают, что объекты, которые в них содержатся, ведут себя как объекты значений, что означает, что при присвоении с помощью оператора присвоения или копировании с помощью конструктора копирования создается новая версия, полностью эквивалентная оригинальной версии.
Это означает, что требуется решить, как должны себя вести уникальные объекты. При создании объекта с уникальным идентификатором и добавлении его в контейнер у вас появятся два объекта с одним и тем же идентификатором при условии, что вы не переопределили оператор присвоения. В операторе присвоения и конструкторе копирования требуется выполнить те действия с уникальным значением, которые имеют смысл для вашего случая. Имеет ли смысл то, что объект в контейнере будет равен оригинальному объекту? Если да, то вполне подойдет стандартный конструктор копирования и оператор присвоения, но вы должны указать это явно, чтобы пользователи вашего класса знали, что вы делаете это намеренно, а не просто забыли, как работают контейнеры. Например, чтобы использовать одно и то же значение идентификатора, конструктор копирования и оператор присвоения должны выглядеть вот так.
UniqueID::UniqueID(const UniqueID& orig) {
id = orig.id;
}
UniqueID& UniqueID::operator=(const UniqueID& orig) {
id = orig.id;
return(*this);
}
Но может возникнуть ситуация, когда в контексте приложения будет иметь смысл создать для объекта в контейнере новое уникальное значение. В этом случае просто снова используйте статическую переменную, как это сделано в обычном конструкторе и показано здесь.
UniqueID::UniqueID(const UniqueID& orig) {
id = ++nextID;
}
UniqueID& UniqueID::operator=(const UniqueID& orig) {
id = ++nextID;
return(*this);
}
Однако трудности еще не закончились. Если UniqueID будет использоваться несколькими потоками, у вас снова возникнут проблемы, так как доступ к статическим переменным не синхронизирован. За дополнительной информацией о работе с ресурсами при наличии нескольких потоков обратитесь к главе 12.
Смотри также
Рецепт 8.3.