Урок №185. Явна спеціалізація шаблону функції

  Юрій  | 

  Оновл. 25 Бер 2021  | 

 26

При створенні екземпляра шаблону функції для певного типу даних компілятор копіює шаблон функції і замінює параметр типу шаблону функції на фактичний (переданий) тип даних. Це означає, що всі екземпляри функції мають одну реалізацію, але різні типи даних. Хоча в більшості випадків це саме те, що потрібно, іноді може знадобитися, щоб реалізація шаблону функції для одного типу даних відрізнялася від реалізації шаблону функції для іншого типу даних.

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

Розглянемо дуже простий шаблон класу:

Вищенаведений код працює з багатьма типами даних:

Результат:

7
8.4

Тепер, припустимо, що нам потрібно, щоб значення типу double (тільки типу double) виводилися в експоненціальному записі. Для цього ми можемо використати спеціалізацію шаблону функції (або «повну/явну спеціалізацію шаблону функції») для створення окремої версії функції print() для виведення значень типу double.

Все просто: записуємо екземпляр шаблону функції (якщо функція є методом класу, то робимо це за межами класу), вказуючи потрібний нам тип даних. Наприклад, ось спеціальний шаблон функції print() для значень типу double:

Коли компілятору потрібно буде створити екземпляр Repository<double>::print(), він побачить, що ми вже явно визначили цю функцію, і тому він використовуватиме саме цей екземпляр, а не копіюватиме загальну для всіх типів даних версію шаблону функції print() .

Частина template <> повідомляє компілятору, що це шаблон функції, але без параметрів (тому що в цьому випадку ми явно вказуємо потрібний нам тип даних).

Результат виконання програми:

7
8.400000e+00

Ще один приклад

Розглянемо ще один випадок, де спеціалізація шаблонів функцій може бути корисна. Наприклад, що станеться, якщо ми спробуємо використати наш шаблон класу Repository з типом даних char*?

Виявляється, замість виведення імені користувача, repository.print() виведе сміття! Чому?

При створенні екземпляру шаблону для типу char*, конструктор Repository<char*> виглядає наступним чином:

Іншими словами, це просто присвоювання вказівника (поверхневе копіювання)! В результаті m_value вказує на ту ж область пам’яті, що і змінна string. А коли ми видаляємо string в main(), то ми видаляємо значення, на яке вказує і m_value! Таким чином, відбувається “витік” пам’яті, і ми отримуємо сміття при спробі виведення m_value.

На щастя, ми можемо це виправити, використовуючи явну спеціалізацію шаблону функції. Замість копіювання вказівника на string, нам потрібно, щоб конструктор копіював саме значення string. Напишемо окремий конструктор для типу даних char*, який саме це і робитиме:

Тепер при виділенні змінної типу Repository<char*> саме цей конструктор буде використовуватися замість стандартного. В результаті m_value отримає свою власну копію string. Отже, коли ми видалимо string в main(), m_value це ніяк не зачепить.

Однак, тепер клас має “витік” пам’яті для типу char*, оскільки m_value не буде видалений, коли змінна repository вийде з області видимості. Як ви вже могли здогадатися, це також можна вирішити, зробивши окремий деструктор для типу char*:

Тепер, коли змінні типу Repository<char*> вийдуть з області видимості, пам’ять, виділена в спеціальному конструкторі, буде видалена в спеціальному деструкторі.

Хоча у всіх прикладах, наведених вище, ми працюємо з методами класу, ви також можете аналогічно виконувати явну спеціалізацію шаблонів звичайних функцій (які не є методами класів).

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (1 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *