Выбрать главу

Это означает, что требуется решить, как должны себя вести уникальные объекты. При создании объекта с уникальным идентификатором и добавлении его в контейнер у вас появятся два объекта с одним и тем же идентификатором при условии, что вы не переопределили оператор присвоения. В операторе присвоения и конструкторе копирования требуется выполнить те действия с уникальным значением, которые имеют смысл для вашего случая. Имеет ли смысл то, что объект в контейнере будет равен оригинальному объекту? Если да, то вполне подойдет стандартный конструктор копирования и оператор присвоения, но вы должны указать это явно, чтобы пользователи вашего класса знали, что вы делаете это намеренно, а не просто забыли, как работают контейнеры. Например, чтобы использовать одно и то же значение идентификатора, конструктор копирования и оператор присвоения должны выглядеть вот так.

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, вызвав его статический метод.

Singleton* p1 = Singleton::getInstance();

И если вы не хотите, чтобы клиенты работали с указателями, то можно возвращать ссылку.

Singleton& Singleton::getInstance() {

 if (inst_ == NULL) {

  inst_ = new Singleton();

 }

 return(*inst_);

}

Важно здесь то, что в обоих случаях клиентам запрещено создавать экземпляры объекта Singleton, и создается единый интерфейс, который предоставляет доступ к единственному экземпляру.

Смотри также

Рецепт 8.3.

8.10. Создание интерфейса с помощью абстрактного базового класса