На предыдущих уроках мы узнали, как использовать параметр типа в шаблоне для создания функций и классов, которые не зависят от определенного типа данных. Однако параметр типа не является единственным параметром, который может иметь шаблон. Шаблоны классов и шаблоны функций могут иметь еще один параметр, известный как параметр non-type.
Параметр non-type
Параметр non-type в шаблоне — это специальный параметр шаблона, который заменяется не типом данных, а конкретным значением. Этим значением может быть:
целочисленное значение или перечисление;
указатель или ссылка на объект класса;
указатель или ссылка на функцию;
указатель или ссылка на метод класса;
В следующем примере мы создадим шаблон класса StaticArray, который использует как параметр типа, так и параметр non-type. Параметр типа отвечает за тип данных элементов статического массива, а параметр non-type отвечает за размер выделяемого массива:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#include <iostream> template <class T, int size> // size является параметром non-type в шаблоне класса class StaticArray { private: // Параметр non-type в шаблоне класса отвечает за размер выделяемого массива T m_array[size]; public: T* getArray(); T& operator[](int index) { return m_array[index]; } }; // Синтаксис определения шаблона метода и самого метода вне тела класса с параметром non-type template <class T, int size> T* StaticArray<T, size>::getArray() { return m_array; } int main() { // Объявляем целочисленный массив из 10 элементов StaticArray<int, 10> intArray; // Заполняем массив значениями for (int count=0; count < 10; ++count) intArray[count] = count; // Выводим элементы массива в обратном порядке for (int count=9; count >= 0; --count) std::cout << intArray[count] << " "; std::cout << '\n'; // Объявляем массив типа double из 5 элементов StaticArray<double, 5> doubleArray; // Заполняем массив значениями for (int count=0; count < 5; ++count) doubleArray[count] = 5.5 + 0.1*count; // Выводим элементы массива for (int count=0; count < 5; ++count) std::cout << doubleArray[count] << ' '; return 0; } |
Результат выполнения программы:
9 8 7 6 5 4 3 2 1 0
5.5 5.6 5.7 5.8 5.9
Примечательно то, что нам не пришлось динамически выделять переменную-член m_array
! Это связано с тем, что для любого созданного объекта класса StaticArray его размер является конкретно заданным значением (можно сказать константой), которое передает пользователь. Например, если мы создадим экземпляр StaticArray<int, 10>
, то компилятор заменит переменную размера массива (size
) на 10. Таким образом, мы получим m_array
типа int[10]
, который можно выделить статическим образом.
Эту особенность использует уже известный нам класс из Стандартной библиотеки С++ — std::array. Когда мы выделяем std::array<int, 5>
, то int
является параметром типа, а 5
— параметром non-type в шаблоне класса!
Зачем в этом примере приведен метод getArray(), ведь он же совсем ничего не делает
Для демонстрации того, как выглядит "Синтаксис определения шаблона метода и самого метода вне тела класса с параметром non-type".
Правильно ли я понимаю, что non-type параметр мы должны знать во время компиляции? Т.е., он обязан быть константой, ведь, например, как в данном случае, мы выделяем на стеке массив размера size, и компилятор должен знать, на сколько передвинуть указатель стека, чтобы поместился этот объект еще до начала выполнения в асм коде? Или же, можно написать так:
Если последний вариант, то размер стека определяется в runtime? Это было бы странно.
Компилятор выдаст ошибку: non-type агрумент шаблона не является константным выражением.
Возник вопрос. В данном примере не определен default constructor. А каким именно образом происходит (в какой последовательности) выделение памяти под массив?
если default constructor класса не определен, то происходит просто последовательная инициализация "по умолчанию" для каждого поля класса. В том порядке, в котором они объявлены в описании класса
Наоборот вначале инициализируются поля, если конечно предоставлены значения, а уже потом в дело вступает конструктор и если его нет то будет предоставлен дефолтный который ничего не делает.
Немного не так — при компиляции создается конструктор по умолчанию (если пользователь не задал свой). При создании объекта этот конструктор вызывается, выделяет память под объект, инициализирует поля класса, после этого выполняется "пустое" тело конструктора.