|
Программирование >> Полиморфизм без виртуальных функций в с++
дублироваться, если шаблон используется с одними и теми же аргументами в разных единицах трансляции. Я считал маловероятным, что при раннем (или даже позднем) инстанцировании шаблона возможно будет поискать, не инстанцирован ли тот же шаблон с другими аргументами, и определить, когда можно разделять инстанцированный код полностью или частично. И все же было чрезвычайно важно избежать такого неоправданного увеличения кода, которое встречается при расширении макросов и в языках с примитивным механизмом инстанцирования [Stroustrup, 1988b]: Среди прочего наследование гарантирует разделение кода между различными типами (код невиртуального базового класса разделяется всеми производными от него классами). Экземпляры шаблона не разделяют код, если только не применяется какая-либо своеобразная техника компиляции. Я не питаю надежд на скорое появление такой техники. Но можно ли воспользоваться наследованием для решения проблемы дублирования кода, возникающей из-за применения шаблонов? Для этого потребовалось производить шаблон от обычного класса. Например: template<class Т> class vector { обобщенный тип вектора Т* v; int SZ; public: vector(int); T& elem(int i) { return v[i]; ) T& operator[](int i); ... template<class T> class pvector : vector<void*> { вектор указателей производный от vector<void*> public: pvector(int i) : vector<void*>(i) {) T*& elem(int i) { return (T*&) vector<void*>::elem(i); } T*& operator[](int i) { return (T*&) vector<void*>::operator[](i); } ... pvector<int*> pivec(lOO); pvector<complex*> icmpvec(200); pvector<char*> pcvecOOO); Реализации всех трех классов вектора указателей полностью разделяются. Все они написаны исключительно с помощью наследования и встраивания на основе класса vector<void*>. Реализация же vec tor<void* > - один из серьезных кандидатов на включение в стандартную библиотеку . Благодаря описанной технике удалось предотвратить неоправданное увеличение объема кода. Те, кто не пользовался чем-то подобным (в С++ или в других языках со сходными средствами параметризации типов), сталкивались с неприятным сюрпризом: на дублированный код могли уходить мегабайты памяти даже в программе скромного размера. С другой стороны, я считал важным, чтобы компилятор инстанцировал лишь ге функции-члены шаблона, которые использовались реально. Напри.мер, если есть шаблон Т с функциями f и д, то ко.мпилятор должен инстанцировать только f, если g для данных аргументов шаблона не вызывается. К тому же если вариант функции-члена будет генерироваться при данном наборе аргументов шаблона, только когда эта функция действительно вызывается, то мы повысим гибкость программы [Stroustrup, 1988b]: Рассмотрим vect ог<Т>. Если мы хотим реализовать операцию сортировки, необходимо потребовать, чтобы для типа Т было определено некоторое отношение порядка. Так обстоит дело не для всех типов. Если бы набор операций над Т нужно было задавать в объявлении vector, пришлось бы иметь два типа векторов: один для объектов, имеющих отношение порядка, другой - для остальных. Если же множество операций над Т задавать необязательно, достаточно и одного типа. Разумеется, нельзя будет сортировать векторы из объектов типа gl ob, для которых отношение порядка не определено. Даже при вашей попытке сделать это компилятор отвергнет сгенерированную фyнкциюvector<glob>: :sort () . 15.6. Шаблоны функций Данное средство введено из-за необходимости иметь функции-члены в шаблонах классов, а также потому, что сама концепция шаблонов без него выглядела незаконченной. Конечно, были еще и хрестоматийные примеры, вроде функции sort (). Эндрю Кениг и Алекс Степанов предложили много примеров, доказывающих необходимость шаблонов функций. Са.мым важным стоит считать пример сортировки массива: объявление шаблона функции: template<class Т> void sort(vector<T>&); void f(vector<int>& vi, vector<String>& vs) { sort(vi); sort(vector<int>& v); sort(vs); sort(vector<String>& v); определение шаблона функции template<class Т> void sort(vector<T>S: v) /* Отсортировать элементы в порядке возрастания Алгоритм: пузырьковая сортировка (неэффективный, но очевидный) */ { unsigned int n = v.sizeO; for (int i = 0; i<n-l; i++) for (int j=n-l; i<j; j--) if (v[j] < v[j-l]) { переставить местами v[j] и v[j-l] Т temp = v[j] v[j] = v[j-l] v[j-l] = temp Как и ожидалось, шаблоны функций оказались незаменимы для поддержки шаблонов классов, когда сервисы предоставлялись обычными функциями, а не функциями-членами (например дружественными функциями, см. раздел 3.6.1). Далее рассматриваются детали реализации шаблонов функций. 15.6.1. Выведение аргументов шаблона функции Для шаблонов функций не нужно задавать аргументы шаблона - компилятор сам выводит их по фактическим параметрам, переданным при вызове. Разумеется, каждый аргумент шаблона, не специфицированный явно (см. раздел 15.6.2), должен однозначно определяться исходя из фактического параметра. В ходе стандартизации стало ясно, что необходимо точно определить, насколько умно должен вести себя компилятор при выведении аргументов шаблона из фактических параметров функции. Например, допустимо ли следующее: template<class Т, int i> Т lookup(Buffer<T,i>& b, const char* p); int f(Buffer<int,128>S: buf, const char* p) { return lookup(buf,p); использовать lookupO, где 111- это int, a i - 128 Ранее ответ на вопрос был отрицательным, поскольку аргументы, не являющиеся типами, нельзя было вывести. Это означает, что невозможно определить не являющуюся членом невстраиваемую функцию, которая применялась бы к шаблону класса, принимающего в качестве аргумента не-тип. Например: template<class Т, int i> class Buffer { friend T lookup(Buffers, const char*); . .. Здесь требуется функция, определение которой раньше было незаконно. После пересмотра данного предложения перечень конструкций, допустимых в списке аргументов шаблона функции, стал выглядеть так: const Т volatile Т Т& Т[п]
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |