|
Программирование >> Полиморфизм без виртуальных функций в с++
Было бы неразумно требовать, чтобы проектировщик класса предоставлял все функции, которые в будущем, возможно, пригодятся автору щаблона. Предвидеть все невозможно. Поэтому понятие зависит от аргумента щаблона Т должно опираться на контекст в точке инстанцирования, по крайней мере, в такой степени, чтобы уметь находить глобальные функции, используемые вместе с Т. При этом неизбежно появляется возможность случайно подобрать какую-нибудь лишнюю функцию. Но эта проблема не слишком серьезна. Мы определим зависимость от аргумента шаблона Т наиболее стандартным способом. Именно так, вызов функции зависит от аргумента шаблона, если был разрешен по-другому или вообще не разрешен в случае отсутствия в программе фактического типа шаблона. Для компилятора проверить такое условие относительно просто. Вот примеры вызовов, зависящих от аргумента шаблона Т: □ у вызванной функции есть формальный параметр, зависящий от Т в соответствии с правилами выведения типа (см. раздел 15.6.1). Например, f (Т), f(vector<T>), f(const Т*); □ тип фактического аргумента зависит от Т в соответствии с правилами выведения типа(см.раздел 15.6.1).Так, f (Т(1) ),f (t),f (g(t) ) и f (&t),ecли предположить, что t - это Т; □ вызов разрешается использованием преобразования к типу т, хотя ни фактический аргумент, ни формальный параметр вызываемой функции не принадлежат типу, зависящему от Т, так, как в первом и втором случаях. Последний пример взят из реальной программы, и зависящий от этого правила код был вполне удачен. Вызов f (1), на первый взгляд, не зависит от Т, как не зависит от Т и функция f (В), к которой идет обращение. Но тип Т аргумента шаблона имел конструктор из типа int и являлся производным от В, поэтому f (1) разрешалось как f (В (Т (1) ) ). 15.10.2.2. Неоднозначности Что надо было бы сделать, если в точке #1 (точка определения шаблона в примере из раздела 15.10.2) и в точке #2 (точка использования) обнаруживаются различные функции? Мы могли бы: □ отдать предпочтение #1; □ предпочесть #2; □ выдать ошибку. Отметим, что в точке #1 можно искать только не-функции и функции, для которых типы аргументов уже известны в точке использования в определении шаблона. Поиск же остальных имен откладывается до точки #2. Первоначальное правило требует предпочесть точку #2, откуда следует, что применимы обычные требования разрешения неоднозначности. Ведь только в случае, когда в точке #2 найдено лучшее соответствие, могут возникнуть расхождения с тем, что найдено в точке #1. К сожалению, при этом приходится не верить собственным глазам, читая определение шаблона. Например: double sqrt(double); template<class T> void f(T t) { double sq2 = sqrt(2); . . . Кажется очевидным, что sqrt (2) вызовет sqrt (double). Ho в точке #2 вполне может обнаружиться функция sqrt (int). В большинстве случаев это неважно, так как правило должно зависеть от аргумента шаблона гарантирует использование именно очевидного разрешения в пользу sqrt (double). Однако если бы Т был равен int, то вызов sqrt (2) зависел бы от аргумента шаблона, так что вызов разрешился бы в пользу sqrt (int). Это неустранимое следствие того, что мы принимаем во внимание точку #2, но, по-моему, оно вызывает много путаницы. Хотелось бы как-то решить эту проблему. С другой стороны, я считал необходимым отдавать предпочтение именно точке #2, ибо только тогда можно разрешить использование членов базового класса таким же способом, как при работе с обычными (нешаблонными) классами. Например: void g(); template<class Т> class X : public Т { void f() { g(); } ... Если в Т есть функция-член g (), следовало бы вызывать именно эту g (), поскольку так ведут себя нешаблонные классы: void g() : class Т { public: void g(); }; class Y : public T { void f() { g(); } вызывается T::g ... С другой стороны, в самых типичных случаях то, что найдено в точке # 1, обычно корректно. Так работает в C-i-i- поиск глобальных имен, именно такая модель позволяет на ранних стадиях обнаруживать большую часть ошибок, предварительно компилировать большинство шаблонов и именно такой механизм защищает от случайного заимствования имен в контексте, неизвестном автору шаблона. Несколько разработчиков компиляторов, особенно Билл Гиббоне, убедительно доказывали, что предпочтение следует отдать точке #1. Какое-то время я склонялся к тому, чтобы считать ошибкой нахождение разных функций в двух данных точках, но это лишь усложняет задачу разработчиков компиляторов, не давая ощутимых выгод пользователям. Кроме того, оказалась бы возможной ситуация, когда употребление определенных имен в контексте использования шаблона портит его удачный код, написанный програм.м истом, думающим, что будут использоваться имена из области действия в точке определения шаблона. В конце концов нашелся довод, который окончательно склонил меня отдавать предпочтение то.му, что было найдено в точке #1. Оп состоял в следующем: некоторый весьма запутанный пример мог быть тривиально разрешен автором шаблона. Сравните: double sqrt(double); template<class Т> void f(T t) { ... sqrt(2); разрешается в точке #1 sqrt(T(2)); очевидно зависит от Т привязка в точке #2 int g(); template<class Т> class X : public Т ( void fO { g(); разрешается в точке #1 T::g(); очевидно зависит от Т привязка в точке #2 ... От автора шаблона требуется более явно выражать свое намерение, когда он хочет использовать некоторую функцию, не видимую в определении шаблона. Похоже, мы достигаем разумного поведения по у.молчанию. 15.103. Специализация Шаблон описывает, как определяется функция или класс при любых значениях его аргументов. Например, шаблон template<class Т> class Comparable { .. . int operator==(const T& a, const T& b) { return a==b; } означает, что для каждого типа Т элементы сравниваются с помощью оператора ==. К сожалению, это слишком ограничительное условие. В частности, в С строки, представленные типом char*, обычно сравниваются функцией strcmpO.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |