Обзор универсальных шаблонов в C++/CLI

Универсальные шаблоны — это параметризованные типы, поддерживаемые средой CLR. Параметризованный тип — это тип, определяемый неизвестным параметром типа, который задается при использовании универсального шаблона.

Преимущества универсальных шаблонов

C++ поддерживает обычные шаблоны, которые наряду с универсальными поддерживают параметризованные типы для создания классов типизированных коллекций. Однако обычные шаблоны предусматривают параметризацию во время компиляции. Нельзя ссылаться на сборку, содержащую определение шаблона, и создавать его новые специализации. После компиляции специализированный шаблон выглядит подобно любому другому классу или методу. В отличие от обычных, универсальные шаблоны создаются в MSIL в виде параметризованного типа, известного среде выполнения как параметризованный тип; исходный код, ссылающийся на сборку, которая содержит универсальный тип, может создавать специализации этого универсального типа. Дополнительные сведения, касающиеся сравнения обычных и универсальных шаблонов Visual C++, см. в статье Generics and Templates (C++/CLI) (Универсальные и обычные шаблоны (C++/CLI)).

Универсальные функции и типы

Типы классов могут быть универсальными, пока являются управляемыми. Примером может служить класс List. Типом объекта в этом списке является параметр типа. Если вам требовался класс List для различных типов объектов, то до универсальных шаблонов вы могли использовать List, принимающий System::Object в качестве типа элемента. Однако при этом в списке могут использоваться любые объекты (в том числе неверного типа). Такой список называется классом нетипизированных коллекций. В лучшем случае можно проверять тип во время выполнения и создавать исключение. Можно также использовать шаблон, который при компиляции в сборку будет терять свою универсальность. Пользователи вашей сборки не смогут создавать собственные специализации шаблона. Универсальные шаблоны позволяют создавать классы типизированных коллекций, например List<int> (читается как "список типа int") и List<double> ("список типа double"), которые при попытке поместить тип в типизированную коллекцию, не предназначенную для хранения такого типа, вызывают ошибку времени компиляции. Кроме того, после компиляции этих типов они остаются универсальными.

Описание синтаксиса универсальных классов можно найти в статье Generic Classes (C++/CLI) (Универсальные классы (C++/CLI)). Новое пространство имен System.Collections.Generic представляет набор параметризованных типов коллекций, включая Dictionary<TKey,TValue>, List<T> и LinkedList<T>.

Экземплярные и статические функции членов классов, делегаты и глобальные функции также могут быть универсальными. Универсальные функции могут потребоваться, если тип параметров функции неизвестен или сама функция должна работать с универсальными типами. Если ранее в качестве параметра для неизвестного типа объекта использовался параметр System::Object, вместо него во многих случаях можно использовать параметр универсального типа, обеспечивающий более типобезопасный код. Любая попытка передать тип, который функция не поддерживает, во время компиляции отмечается как ошибка. При использовании System::Object в качестве параметра функции случайная передача объекта, обработка которого функцией не предусмотрена, не обнаруживается. Неизвестный тип объекта требуется привести к конкретному типу в теле функции, а также учесть возможность появления исключения InvalidCastException. При наличии универсального шаблона код, пытаясь передать объект в функцию, вызывает конфликт типов и поэтому тело функции будет гарантированно содержать правильный тип.

Те же преимущества относятся к классам коллекций, созданным в универсальных шаблонах. В прошлом в классах коллекций System::Object использовался для хранения элементов в коллекции. Вставка объектов типа, для которого коллекция не предназначена, во время компиляции не отмечалась и часто не отмечалась даже после вставки объектов. Обычно объект приводится к другому типу после обращения к нему в коллекции. Неизвестный тип обнаруживается только при сбое приведения. Универсальные шаблоны решают эту проблему во время компиляции, обнаруживая любой код, в котором вставляется тип, не соответствующий параметру типа универсальной коллекции (или неявно преобразованный в него).

Описание синтаксиса см. в статье Универсальные функции (C++/CLI).

Терминология, используемая с универсальными шаблонами

Параметры типа

Универсальное объявление содержит один или несколько неизвестных типов, называемых параметрами типа. Имя параметра типа представляет тип в теле универсального объявления. Параметр типа в теле универсального объявления используется в качестве типа. Универсальное объявление списка List<T> содержит параметр типа T.

Аргументы типа

Аргумент типа — это фактический тип, используемый вместо параметра типа, когда универсальный шаблон определен для конкретного типа или типов. Например, int является аргументом типа в List<int>. В качестве аргумента универсального типа разрешается использовать только типы значений и типы дескрипторов.

Сконструированный тип

Тип, созданный из универсального типа, называется сконструированным. Не полностью заданный тип (например, List<T>) называется открытым сконструированным типом, а полностью заданный (например, List<double>,) — закрытым сконструированным типом или специализированным типом. Открытые сконструированные типы могут использоваться в определении других универсальных типов или методов и не могут быть полностью заданы до тех пор, пока не задан сам включающий универсальный шаблон. Например, ниже показано использование открытого сконструированного типа в качестве базового класса для универсального шаблона.

// generics_overview.cpp
// compile with: /clr /c
generic <typename T>

ref class List {};

generic <typename T>

ref class Queue : public List<T> {};

Ограничение

Под ограничением понимается ограничение для типов, которые могут использоваться в качестве параметра типа. Например, заданный универсальный класс может принимать только классы, производные от указанного класса или реализующие указанный интерфейс. Дополнительные сведения см. в статье Constraints on Generic Type Parameters (C++/CLI) (Ограничения, применяемые к параметрам универсальных типов (C++/CLI)).

Ссылочные типы и типы значений

В качестве аргументов типа могут использоваться типы дескрипторов и типы значений. В универсальном определении, в котором может использоваться любой тип, применяется синтаксис ссылочных типов. Например, оператор -> служит для доступа к элементам типа параметра типа независимо от того, является ли используемый в конечном счете тип ссылочным типом или типом значения. Если в качестве аргумента типа применяется тип значения, среда выполнения создает код, в котором типы значений используются непосредственно, без упаковки-преобразования.

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

// generics_overview_2.cpp
// compile with: /clr
generic <typename T>

ref class GenericType {};
ref class ReferenceType {};

value struct ValueType {};

int main() {
    GenericType<ReferenceType^> x;
    GenericType<ValueType> y;
}

Параметры типа

Параметры типов в универсальном классе обрабатываются так же, как и другие идентификаторы. Однако поскольку тип неизвестен, существуют ограничения по их использованию. Например, нельзя использовать члены и методы класса параметра типа, если неизвестно, поддерживает ли параметр типа эти члены. То есть для доступа к члену через параметр типа необходимо добавить тип, содержащий этот член, в список ограничений параметра типа.

// generics_overview_3.cpp
// compile with: /clr
interface class I {
   void f1();
   void f2();
};

ref struct R : public I {
   virtual void f1() {}
   virtual void f2() {}
   virtual void f3() {}
};

generic <typename T>
where T : I
void f(T t) {
   t->f1();
   t->f2();
   safe_cast<R^>(t)->f3();
}

int main() {
   f(gcnew R());
}

Эти ограничения также относятся к операторам. В произвольном параметре универсального типа нельзя использовать операторы == и != для сравнения двух экземпляров параметра типа, если тип не поддерживает эти операторы. Эти проверки необходимы для универсальных шаблонов, но не требуются для обычных шаблонов, поскольку универсальные шаблоны могут быть конкретизированы во время выполнения любым другим классом, удовлетворяющим ограничениям, когда слишком поздно проверять использование недопустимых членов.

Экземпляр по умолчанию параметра типа можно создать с помощью оператора (). Например:

T t = T();

где T — параметр типа в определении универсального класса или метода; инициализирует переменную со значением по умолчанию. Если параметр T является ссылочным классом, он будет пустым указателем; если T — класс значения, объект инициализируется с нулевым значением. Этот параметр называется инициализатором по умолчанию.

См. также

Универсальные шаблоны