Дизайн-патерни: Синглтон (С++)

3 хв. читання

Одинак (або синглтон) – останній зі списку породжуючих патернів. Саме про нього піде мова у статті.

Синглтон (Singleton)

Хоча singleton перекладається як одинак, українські програмісти не звикли використовувати таку назву і називають такий патерн просто синглтоном.

Синглтон фактично є найпростішим патерном, про нього запитують майже на всіх співбесідах і про нього вже чули ті, хто ще не знайомий з іншими дизайн-патернами.

Призначення: створення єдиного екземпляра класу і забезпечення доступу до нього в глобальному контексті.

Часто стає в нагоді при проектуванні програми, в якій повинні бути спільні ресурси для всіх її компонентів. Наприклад, створення системи логування, яка записуватиме всі дані в єдиний простір, попри те, в якому місці програми викликаються її методи. Широко застосовується при написанні драйверів.

Для того, щоби обмежити створення об'єктів, достатньо зробити конструктор приватним, що якраз і є чіткою ознакою синглтона.

Загалом класичний синглтон має вигляд:

class ClassicSingleton
{
public:
	static ClassicSingleton* getInstance();

private:
	ClassicSingleton();
	ClassicSingleton(const ClassicSingleton& cs) = delete;
	ClassicSingleton& operator=(const ClassicSingleton& cs) = delete;
	~ClassicSingleton();

	static ClassicSingleton* m_pInstance;
};

Як видно, конструктор і деструктор приватні. При цьому конструктор копіювання й operator= не повинні бути реалізовані. Це вказується за допомогою delete. В приватному просторі також створюється об'єкт даного класу – m_pInstance. Це єдиний екземпляр класу, який повертатиметься за допомогою функції getInstance(), реалізація якої наступна:

ClassicSingleton* ClassicSingleton::m_pInstance = 0;  
ClassicSingleton* ClassicSingleton::getInstance()
{
	if (m_pInstance == nullptr)
		m_pInstance = new ClassicSingleton();
	return m_pInstance;
}

Якщо умова m_pInstance == nullptr істинна (а вона буде істинна при першому виклику функції getInstance()), то викликається конструктор, який ініціалізує об'єкт m_pInstance, після чого функція повертає вказівник на нього. В іншому випадку, якщо умова не істинна (а це означає, що m_pInstance вже сконструйований), повертається тільки вказівник на вже створений об'єкт.

Такий код не новий, але актуальний і навряд чи застаріє колись.

Другий спосіб написання синглтона – Синглтон Майєрса:

class MeyersSingleton
{
public:
	static MeyersSingleton& getInstance();

private:
	MeyersSingleton();
	MeyersSingleton(const MeyersSingleton& ms) = delete;
	MeyersSingleton& operator=(const MeyersSingleton& ms) = delete;
	~MeyersSingleton();
};

Цей спосіб дуже лаконічно використовує можливості мови С++. В даному випадку функція getInstance() повертає посилання на об'єкт класу, а не вказівник, як у класичному синглтоні. А реалізація цієї функції наступна:

MeyersSingleton& MeyersSingleton::getInstance()
{
	static MeyersSingleton instance;
	return instance;
}

Коротко і просто. Статичні дані можна ініціалізувати тільки один раз, чим Майєрс і скористався. При першому виклику функції getInstance() створиться об'єкт instance і повернеться посилання на нього. При другому і наступних викликах об'єкт instance вже не створюватиметься, оскільки він статичний. Тоді просто повертатиметься посилання на об'єкт, який вже створено.

Розглянемо принцип роботи синглтона більш детально.

class ClassicSingleton
{
public:
	static ClassicSingleton* getInstance();
	void setValue(int val);
	void outputVal();

private:
	ClassicSingleton();
	ClassicSingleton(const ClassicSingleton& cs) = delete;
	ClassicSingleton& operator=(const ClassicSingleton& cs) = delete;
	~ClassicSingleton();

	static ClassicSingleton* m_pInstance;

	int m_someval;
};

Розширимо клас ClassicSingleton, додавши член даних m_someval, який зберігатиме якесь число. А також функцію setValue(int val), котра встановлюватиме значення для m_someval і другу функцію outputVal(), яка виводитиме дане число.

Заодно розглянемо використання синглтона Майєрса.

int main()
{
	int value;

	cout << "Stage 1" << endl;
	ClassicSingleton* cs = ClassicSingleton::getInstance();
	cout << "Classic cs (address): " << cs << endl;
	cout << "Set m_someval of cs: ";
	cin >> value;
	cs->setValue(value);
	cout << "Get m_someval of cs: ";
	cs->outputVal();

	cout << "\
Stage 2" << endl;
	MeyersSingleton& ms = MeyersSingleton::getInstance();
	cout << "Meyers' ms (address): " << &ms << endl;

	cout << "\
Stage 3" << endl;
	ClassicSingleton* cs2 = ClassicSingleton::getInstance();
	cout << "Classic cs2 (address): " << cs2 << endl;
	cout << "Get m_someval of cs2: ";
	cs2->outputVal();

	cout << "\
Stage 4" << endl;
	MeyersSingleton& ms2 = MeyersSingleton::getInstance();
	cout << "Meyers' ms (address): " << &ms2 << endl;

	return 0;
}

Результат виконання:

Singleton Execution

Етап 1:

Створюється об'єкт cs. Зверніть увагу на процес інстанціювання:

ClassicSingleton* cs = ClassicSingleton::getInstance();

getInstance() повертає вказівник на єдиний екземпляр класу m_pInstance, при цьому звернення до функції відбувається через ім'я класу. Це значить, отримати доступ до екземпляра синглтона можна на глобальному рівні.

Після створення виводиться адреса, яку тримає cs, а саме адреса m_pInstance.

Далі вводиться значення value - 26, яке передається через setValue(value) до члена даних класу і згодом виводиться.

Етап 2:

Створюється екземпляр синглтона Майєрса і виводиться його адреса.

Етап 3:

Створюється ще один об'єкт класу ClassicSingletoncs2. Після виводу адреси, бачимо що вона така ж як і в першому етапі. Отже, як і говорилося, екземпляр класу єдиний, а всі інші об'єкти того самого класу вказують на цей єдиний екземпляр.
Викликавши функцію cs2->outputVal(), отримаємо число 26, як і в першому випадку.

Етап 4:

Теж саме і для ms2 – адреса збігається з тією, що отримана на другому етапі.

Синглтон Майєрса більш безпечний, оскільки в класичному синглтоні необхідно слідкувати за пам'яттю. Однак і синглтон Майєрса може створити проблеми при багатопоточності та інстанціюванні похідних класів. Але це окрема і надто велика тема для розгляду в цій статті.

Підсумки

Синглтон забезпечує створення єдиного екземпляра класу і глобального доступу до нього.

Алгоритм використання:

  1. Оголосити конструктор класу приватним, та заборонити визначення конструктора копіювання і оператора присвоєння.
  2. Створити статичний екземпляр (для класичного синглтона) та функцію, що керує ініціалізацією та повертає адресу єдиного екземпляра.
  3. Отримувати доступ до екземпляра класу на глобальному рівні за допомогою імені класу.

Вихідний код до статті доступний тут.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація