Требуется, чтобы каждый объект класса имел уникальный идентификатор.
Решение
Для отслеживания следующего доступного для использования идентификатора используйте статическую переменную-член. В конструкторе присвойте текущему объекту очередное доступное значение, а затем инкрементируйте статическую переменную. Чтобы понять, как это работает, посмотрите на пример 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.
8.9. Создание Singleton-класса
Проблема
Имеется класс, который должен иметь только один экземпляр, и требуется предоставить способ доступа к этому классу из клиентского кода таким образом, чтобы каждый раз возвращался именно этот единственный объект. Часто это называется шаблоном singleton или singleton-классом.
Решение
Создайте статический член, который указывает на текущий класс, ограничьте использование конструкторов для создания класса, сделав их private, и создайте открытую статическую функцию-член, которая будет использоваться для доступа к единственному статическому экземпляру. Пример 8.9 демонстрирует, как это делается.
Пример 8.9. Создание singleton-класса
#include <iostream>
using namespace std;
class Singleton {
public:
// С помощью этого клиенты получат доступ к единственному экземпляру
static Singleton* getInstance();
void setValue(int val) {value_ = val;}
int getValue() {return(value_);}
protected:
int value_;
private:
static Singleton* inst_; // Единственный экземпляр
Singleton() : value_(0) {} // частный конструктор
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
};
// Определяем указатель
static Singleton Singleton* Singleton::inst_ = NULL;
Singleton* Singleton::getInstance() {
if (inst_ == NULL) {
inst_ = new Singleton();
}
return(inst_);
}
int main() {
Singleton* p1 = Singleton::getInstance();
p1->setValue(10);
Singleton* p2 = Singleton::getInstance();
cout << "Value = " << p2->getValue() << 'n';
}
Обсуждение
Существует множество ситуаций, когда требуется, чтобы у класса существовал только один экземпляр. Для этой цели служит шаблон Singleton. Выполнив несколько простых действий, можно реализовать singleton-класс в С++.
Когда принимается решение, что требуется только один экземпляр чего-либо, то на ум сразу должно приходить ключевое слово static. Как было сказано в рецепте 8.5, переменная-член static — это такая, которая может иметь в памяти только один экземпляр. Для отслеживания единственного объекта singleton-класса используйте переменную-член static, как сделано в примере 8.9.
private:
static Singleton* inst_;
Чтобы клиентский код ничего про нее не знал, сделайте ее private. Убедитесь, что в файле реализации она проинициализирована значением NULL.
Singleton* Singleton::inst_ = NULL;
Чтобы запретить клиентам создавать экземпляры этого класса, сделайте конструкторы private, особенно конструктор по умолчанию.
private:
Singleton() {}
Таким образом, если кто-то попробует создать в куче или стеке новый singleton-класс, то он получит дружественную ошибку компилятора.
Теперь, когда статическая переменная для хранения единственного объекта Singleton создана, создание объектов Singleton ограничено с помощью ограничения конструкторов; все, что осталось сделать, — это предоставить клиентам способ доступа к единственному экземпляру объекта Singleton. Это делается с помощью статической функции-члена.
Singleton* Singleton::getInstance() {
if (inst_ == NULL) {
inst_ = new Singleton();
}
return(inst_);
}
Посмотрите, как это работает. Если указатель static Singleton равен NULL, создается объект. Если он уже был создан, то возвращается его адрес. Клиенты могут получить доступ к экземпляру Singleton, вызвав его статический метод.