|
Программирование >> Полиморфизм без виртуальных функций в с++
Эндрю Кенига, Мартина ОРиордана, Джонатана Шопиро. К моменту принятия (ноябрь 1993 г., Сан-Хосе) правила уже были всесторонне испытаны на практике. Рассмотрим пример: #include <iostream.h> #include <vector.h> void db(double); # 1 template<class T> T sum(vector<T>& v) { T t = 0; for (int i = 0; i<v.size(); i++) t = t + v[i]; if (DEBUG) { cout << сумма равна << t << \n; db(t); db(i) ; return t; . .. #include <complex.h> # 2 void f(vector<complex>& v) { complex с = sum(v); В первоначальном определении говорилось, что любые имена, встречающиеся в шаблоне, привязываются в точке инстанцирования, она находится прямо перед глобальным объявлением, в котором шаблон используется в первый раз (в примере выше - #2). У этого определения есть, по меньшей мере, три нежелательных свойства: □ в точке определения шаблона ничего нельзя проверить. Например, если DEBUG в этой точке не определено, то не будет выдано сообщение об ошибке; □ могут быть найдены и использованы имена, объявленные после определения шаблона. Часто, но не всегда это является неожиданностью для пользователя, читающего определение шаблона. Например, естественно ожидать, что вызов db (i) будет разрешен в пользу объявленной выше функции db (double), но если фрагмент . . . содержит объявление db(int), то в соответствии с общими правилами перегрузки используется именно db (int). С другой стороны, если в файле complex. h определена функция db (complex), мы бы хотели, чтобы вызов db(t) был разрешен в пользу db (compl ex), а не признан ошибкой из-за того, что он является некорректным вызовом функции db (double), видимой в определении шаблона; □ .множество имен, видимых в точке инстанцирования, различается, когда sum используется в двух разных ко.мпиляциях. Если sum (vector<complex>&) при этом получает два разных определения, то результирующая программа незаконна с точки зрения правила одного определения (см. раздел 2.5). Однако в таких случаях проверить выполнение правила одного определения не под силу традиционному компилятору С++. Ко всему прочему в оригинальном правиле пе учтен случай, когда определение шаблона функции не находится в данной единице трансляции. Например; template<class Т> Т sum(vector<T>& v); . . . tinclude <complex.h> # 2 void f(vector<complex>& v) { complex с = sum{v); Ни разработчики компилятора, ни пользователи не получали указаний па то, как искать определения шаблонов функции sum (). Поэто.му в ко.мпиляторах применялись разные стратегии. Общая проблема состоит в том, что в инстанцировании шаблона участвуют три контекста и четко разделить их нельзя; □ контекст определения шаблона; □ контекст объявления типа аргумента; □ контекст использования шаблона. Основная цель проектирования шаблонов - гарантировать наличие достаточного объема инфор.мации при определении пшблопа, чтобы его разрешалось инстанцировать по фактическим аргументам, не полагаясь на случайные обстоятельства, сложившиеся в точке вызова. В первоначальном проекте делалась попытка обеспечить разумное поведение, полагаясь исключительно на правило одного определения (см. раздел 2.5). Считалось, что если случай и повлияет на определение сгенерированной функции, то обстоятельства вряд ли будут складываться одинаково при любом использовании шаблона функции. Предположение обоснованное, но компиляторы обычно не выполняют проверку на непротиворечивость (и тому есть веские причины). Так или иначе, грамотно написанные программы работают. Но желающие ис1Юльзо-вать шаблоны как макросы могут написать такую программу, которая будет извлекать нежелательные, на мой взгляд, преи.мущества из контекста вызова. Кро.ме того, у разработчика компилятора возникают серьезные трудности, когда он хочет синтезировать контекст для определения функции, чтобы ускорить инстанцирование. Уточнить понятие точки инстанцирования так, чтобы 01ю было лучше исходного и одновременно сохраняло работоспособность написанных програ.мм, было необходимо, хотя и непросто. Сначала мы решили потребовать, чтобы каждое встречающееся в шаблоне имя определялось в точке определения шаблона. При этом шаблон стал бы легко читаться, появилась бы гарантия, что при инстанцирований не будет выбрано ничего случайного, а кроме того, обеспечивалось бы раннее обнаружение ошибок. Но это не позволило бы шаблону применять операции к объектам собстве1шого класса. В примере выше +, f () и конструктор Т не определены в точке определения шаблона. Объявление в пшблопе тоже недопустимо, так как нельзя задать их типы. Например, + может быть встроенным оператором, функцией-членом или глобальной функцией. Если это функция, она может принимать аргу.менты типа Т, const Т& и т.д. Это не что иное, как проблема задания ограничений на аргументы шаблона (см. раздел 15.4). Принимая во внимание тот факт, что ни в точке определения шаблона, ни в точке его использования нет достаточного контекста для инстанцирования, мы должны найти компромиссное решение. Оно заключается в то.м, чтобы разделить встречаюищеся в определении шаблона имена на две категории: □ зависящие от аргументов шаблона; □ все остальные. Последние можно привязать в контексте определения шаблона, а первые -в контексте инстанцирования. Данная концепция пе имеет изъянов, если только удастся четко определить, что обо.значает фраза зависит от аргументов шаблона . 15.10.2.1. Определение зависимости от Т Фраза зависит от аргу.мента шаблона Т , вероятно, может обозначать является членом Т . Если Т - встроенный тип, то встроенные операторы будут считаться членами. К сожалению, этого недостаточно. Вот пример: class complex { ... friend complex operator+(complex,complex); complex(double); Чтобы этот пример работал, в определение зависимости от аргумента шаблона т надо включить, по крайней мере, друзей т. Но и этого мало, поскольку ответственные функции, не являюншеся члмшми, не обязательно должны быть дружественными: class complex { ... complex operator+=(complex); complex(double); complex operator+(complex a, complex b) { complex r = a; return r+=b;
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |