CodingClub - клуб для программистов и не только
Главная Новости Статьи FAQ Файлы Книги Форум

Навигация
Проход по ссылкам навигации
Главная страница
Новости
Статьи
FAQ
Книги
Каталог файлов
Поиск по сайту
Наши опросы
Обратная связь
Личный кабинет
О проекте

Категория статьей
C++Builder
Delphi
Visual C++
PHP
Сетевые технологии
MySQL
Oracle
Java
C#
Visual Basic
Access
MS SQL Server
.NET Framework
WEB-дизайн
Assembler
ADO .NET
ASP/ASP .NET
Unix/Linux
Мобильное программирование
.NET
OpenGL
Direct3D
Game development
XML
Сжатия данных
WinForms
WinAPI
Разное
JavaScript
1С:Предприятие 8.0
DirectSound
ActiveX
STL
Экзамен MCP 70-016
MFC
Perl
Программирование в Linux
COM
Lotus
Python
MS Windows
Железо
Silverlight
LINQ
Entity Framework

Друзья



Programmer's Klondike









ForOffice.ru

Miralinks




  


Обобщенное программирование под .NET

В Visual Studio 2005 появилась поддержка модели обобщенного программирования на основе параметризованных типов (type parameter model) для Microsoft .NET Framework. Параметризованные типы (parameterized types), конечно же, известны программистам на C++. Ну а для тех, кто не знаком с такими типами, будет дано краткое введение в обобщенное программирование.

Основная идея обобщенного программирования — создание постоянной кодовой базы, потенциально поддерживающей бесконечный набор типов. Существует две базовые модели обобщенного программирования: универсального контейнера типов (Universal Type Container Model, UTCM) и параметризованных типов (Type Parameter Model, TPM).

В UTCM информация о типе, сопоставленная с объектом, отделяется. Такая модель проста в поддержке, все объекты хранятся одинаково и непрозрачно. В монолитной системе типов, например в Common Type System (CTS), универсальным контейнером типов служит Object; все CTS-типы наследуются либо явно, либо неявно от Object. В языке вроде C универсальным типом является void*.

В TPM связывание информации о типе, сопоставленной с объектом, откладывается. Значения, которые могут варьироваться от одного вызова к другому, выносятся в параметры. Именно поэтому в названии данной модели присутствует словосочетание «параметризованные типы» — она сложнее, но и мощнее.

Например, вы реализуете интерфейс IEnumerator пространства имен System::Collections. Он выглядит весьма тривиально, предоставляя два метода и одно свойство. Но создание единой реализации для всех пользователей — невероятно трудная задача в строго типизированном языке. Болевые точки нашей реализации помечены тремя вопросами подряд (???):

interface class IEnumerator
{
property ??? Current { ??? get(); }
bool MoveNext();
void Reset();
};

Система типов требует статически определить тип, сопоставленный со свойством, а также тип возвращаемого значения для get-аксессора, но это, конечно, невозможно. Ведь пользователям может понадобиться перечисление самых разнообразных типов. Что же делать?

Универсальное решение в UTCM, где контейнером служит Object, проще:

interface class IEnumerator
{
property Object^ Current { Object^ get(); }
bool MoveNext();
void Reset();
};

Это дает вам одну степень отделения (separation) и позволяет поддерживать потенциально бесконечное количество типов с использованием одной постоянной кодовой базы. Реализация выглядит весьма элегантно применительно к пассивному хранению и выборке объектов ссылочных типов. Но она становится отнюдь не такой элегантной, когда вам нужно извлекать и манипулировать объектами как конкретными типами (concrete types). Это требует обратного приведения к исходному типу объекта. К сожалению, при этом у компилятора нет информации о типе, необходимой для гарантированно корректного преобразования, так что программисту придется самому заниматься явным приведением типов, как показано в следующем примере:

extern void f( Object^ anyTypeWorks );
Object^ o = "a string of all things";
// Обратного приведения не требуется — пассивное хранение
f( o );
// А здесь оно требуется
String^ s = safe_cast<String^>( o );

При реализации наборов все становится еще проб-лематичнее. В модели универсального контейнера типов нет возможности статически ограничить набор так, чтобы он содержал объекты только одного типа. Этого можно добиться лишь на уровне программы, но такой путь весьма труден и чреват ошибками. Более того, поскольку это решение на уровне программы, оно применимо только в период выполнения. И вновь вы остаетесь без поддержки со стороны компилятора.

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

Что такое параметризованные типы?

Модель параметризованных типов обеспечивает вторую степень отделения, исключая приведение к нижележащим типам (downcasting) и упаковку (boxing) и поддерживая контроль типов на этапе компиляции. Это решение двухступенчатое. На первом этапе вы указываете параметр типа (type parameter) как шаблон подстановки истинного типа по аналогии с тем, как это делается с функцией:

interface class IEnumerator
{
property typeParameter Current { typeParameter get(); }
bool MoveNext();
void Reset();
}

На втором этапе вы сообщаете компилятору, что typeParameter — шаблон подстановки, а не реальная сущность. Для этого вы указываете обобщенную сигнатуру (generic signature), называемую списком параметров (parameter list). В C++/CLI это делается с помощью ключевого слова generic, чтобы задействовать механизм поддержки обобщений в общеязыковой исполняющей среде (common language runtime, CLR), или template, чтобы использовать механизм шаблонных типов C++, как показано на рис. 1.

Это дает вам определение интерфейса, независимое от типа. При реализации интерфейса IEnumerator вы должны предоставить истинный тип, который связывается с шаблоном подстановки typeParameter. Для этого имя параметризованного класса указывается с истинным типом в угловых скобках, например IEnumerator<int>.

C++/CLI поддерживает два механизма параметризованных типов — шаблоны (templates) и обобщения (generics) — для определения параметризованных ссылочных, значимых и интерфейсных классов, а также функций и делегатов. Внешне определения параметризованного обобщенного или шаблонного типа синтаксически эквивалентны (меняется лишь ключевое слово — template или generic), но внутренне они заметно различаются.

Рассмотрим два объявления стека на рис. 2: шаблонный экземпляр (tStack), который использует контейнер динамических векторов, предоставляемый CLI-реализацией Standard Template Library (STL), и обобщенный экземпляр (gStack), который задействует контейнер динамического List<T>, предоставляемый пространством имен System::Collections::Generic. Обе библиотеки параметризованных типов-наборов являются новинками Visual Studio 2005.

Вы создаете экземпляр параметризованного типа, указывая истинный тип в угловых скобках за именем класса. Например, на рис. 3 показан шаблонный стек (template stack), экземпляр которого создается сначала с аргументом для целочисленного типа, потом с аргументом для строкового типа. То же самое, но применительно к обобщенному стеку (generic stack) демонстрирует рис. 4.

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

Здесь обобщенное и шаблонное определения, а также экземпляры специфических типов на их основе почти эквивалентны, но так бывает отнюдь не всегда — иначе не было бы смысла поддерживать в C++/CLI оба механизма. Различия вы увидите, когда мы углубимся в детали этих механизмов в последующих колонках.

Список параметров типов

Каждый параметр типа (type parameter) предваряется ключевым словом class или typename. Эти ключевые слова никак не увязаны с конкретной платформой, например, class не подразумевает, что это неуправляемый тип (native type), а typename — что это CLI-тип. Оба просто указывают, что следующее имя является шаблоном подстановки параметризованного типа (parameterized type placeholder) и что оно будет заменено аргументом-типом (type argument), заданным пользователем.

Причины появления двух ключевых слов чисто исторические. В исходной спецификации шаблонов для задания параметра типа Страуструп решил использовать имеющееся ключевое слово class и не вводить новое, чтобы не повредить существующим программам. И вплоть до появления стандарта ISO-C++ ключевое слово class было единственным способом объявления параметра типа.

Повторное использование существующих ключевых слов, похоже, всегда создает путаницу. Что означает ключевое слово class — указывает, что аргументы-типы могут быть только классами, а не, скажем, встроенными типами или типами указателей? Нет. Тогда получается, что в данном контексте ключевое слово class ведет к неверному толкованию кода? Да. Явно нужно было ввести новое ключевое слово, чтобы избежать путаницы. И все же ключевое слово typename появилось не по этой причине.

Его добавили в C++ скорее для того, чтобы обеспечить синтаксический разбор определений шаблонов. Эта тематика несколько выходит за рамки моей статьи, и я лишь кратко коснусь ее. Более полное описание вы найдете в книге Страуструпа «Design and Evolution of C++» (Addison-Wesley, 1994).

Компилятор не всегда может различить объявления типа и выражения. Если компилятор встречает выражение Parm::name в определении шаблона, где Parm — параметр шаблонного типа, представляющий класс, то к чему относится name — к члену типа или полю данных Parm?

template <class Parm, class U>
Parm minus( Parm* array, U value )
{
Parm::name * p; // Это объявление указателя или выражение
// умножения? По умолчанию интерпретируется
// как выражение.
}

По умолчанию этот оператор интерпретируется как выражение, где перемножаются операнды Parm::name и p. Ключевое слово typename было введено, чтобы программисты могли переопределять интерпретацию по умолчанию. Например, для объявления p указателем типа Parm::name вы должны переписать шаблонную функцию так:

template <class Parm, class U>
Parm minus( Parm* array, U value )
{
typename Parm::name * p; // теперь это объявление указателя
}

Теперь, когда появилось это новое ключевое слово, вы можете легко избавиться от путаницы, создаваемой ключевым словом class. Но отказываться от старого ключевого слова тоже не стали, так как оно уже используется в огромной кодовой базе, массе книг, статей и других материалов. Вот почему C++ поддерживает оба ключевых слова.

За ключевым словом class или typename указывается идентификатор, который служит для подстановки внутри шаблонного или обобщенного определения. Каждый идентификатор в списке параметров должен быть уникальным. Однако в разных объявлениях одного типа ключевое слово и идентификатор могут варьироваться:

template <class T>
public ref class tStack;
// В разных объявлениях одного типа ключевое слово
// и идентификатор могут варьироваться
template <typename elemType>
public ref class tStack {};

Область видимости идентификатора ограничивается объявлением типа. В первом объявлении tStack она заканчивается на точке с запятой; на это имя нет ссылок. В реальном определении идентификатор виден как внутри определения класса, так и внутри каждой функции-члена данного определения класса.

Создание экземпляра типа

Шаблонное или обобщенное определение указывает, как конструировать индивидуальные классы или функ-ции с набором из одного или нескольких реальных типов. Одно из основных различий между шаблонами и обобщениями — момент создания экземпляра. Создание экземпляра шаблона происходит на этапе компиляции, а создание экземпляра обобщения осуществляется CLR в период выполнения. (Об этом мы поговорим подробнее в следующей колонке.)

Шаблонное определение служит схемой для автоматического создания экземпляров специфических типов; компилятор буквально подключает предоставленные пользователем аргументы — специфические типы. Обобщенное определение больше похоже на кальку; исполняющая среда конструирует экземпляры специфических типов, модифицируя универсальный синтаксис на основе того, содержит аргумент-тип ссылочный или значимый тип. Например, класс стека (stack class) для объектов типа int и класс стека для объектов типа String создаются автоматически из шаблонного или обобщенного определения, когда вы пишете следующее:

tStack<int> ^si;
tStack<String^> ^ss;

Генерация класса по шаблонному определению называется созданием экземпляра шаблона (template instantiation) — именно такой термин упоминается в стандарте ISO-C++. В литературе по тематике обобщений генерация класса называется конструированием (construction), что опять же соответствует различиям между шаблоном и калькой. Но здесь я использую для описания этого процесса термин «создание экземпляра» (instantiation). При создании экземпляра класса стека для объектов типа String каждое вхождение параметра шаблона внутри обобщенного или шаблонного определения заменяется типом String. И корректность определения проверяется применительно к этому типу.

Сигнатура экземпляра выглядит как Stack<int> или Stack<String^>. Маркеры <int> или <String^>, следующие за именем, называются в стандарте ISO-C++ аргументами шаблона (template arguments). В литературе по обобщениям они называются аргументами-типами (type arguments), и в данном случае я буду следовать этой терминологии. Аргументы-типы указываются в списке и разделяются запятыми, а сам список заключается в угловые скобки. В сигнатуре экземпляра аргументы-типы всегда должны задаваться явно. В отличие от экземпляров функций экземпляры классов никогда не отделяются от контекста, в котором они используются. Что это означает, я поясню в одной из будущих колонок по параметризованным функциям.

Создание экземпляров класса возможно в универсальной программе везде, где применим и непараметризованный тип класса. То же самое относится к их объектам.

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

Этим в основном исчерпываются общие характеристики шаблонов и обобщений. Конечно, интереснее их различия. Об одном таком отличии я уже упоминал: экземпляры шаблонов создаются при компиляции, а экземпляры обобщений — в период выполнения самой исполняющей средой. Это влечет за собой весьма далеко идущие последствия, что и станет темой моей очередной колонки. До встречи!

Вопросы и комментарии (на английском языке) присылайте по адресу purecpp@microsoft.com.

Стэнли Б. Липмен (Stanley B. Lippman) — архитектор в группе Visual C++ в Microsoft. Он начинал работать над C++ вместе с его автором, Бьёрном Страуструпом (Bjarne Stroustrup), в 1984 году в Bell Laboratories. С тех пор и до перехода в Microsoft работал в области анимации на студиях Disney и DreamWorks, был ведущим консультантом в JPL, а также техническим директором по программному обеспечению на «Fantasia 2000».


Оценить:
Автор: Стэнли Б. Липмен (msdn magazine/русская редакция)
Источник:

Дата: 29 января 2007 г. | Прочитана: 918 | Комментариев: 0 | Рейтинг: 4



Комментарии у данной статьи нет. Можете сделать это первым ;)


Вы не зарегистрированы или не авторизовались и не можете оставить комментарий.
  
Панель пользователя
Авторизация
Логин:
Пароль:
 
 
   Регистрация
Напомнить пароль

Реклама

Кто на сайте
anony Гостей:  7
member Пользователей:  0
group Всего:  7
Пользователи:

Стоит побывать
Заработай на своем сайте

Наше голосование
На каком языке вы пишете?












Просмотреть результат

Всего голосов: 32
Комментариев: 0


Наш домик

Поможем нашему домику на "карте интернета" расположиться поближе к центру мира!
Internet Map
(С Вас - 1 клик/сутки)


Рассылка на E-mail





Подписались на рассылку

  

  

Copyright © 2005-2008 CodingClub - клуб для программистов и не только
Разработка: Программист

Рейтинг@Mail.ru Rambler's Top100