Главная страница Библиотека (скачать книги) Скачать софт Введение в программирование Стандарты для C++ Уроки по C# Уроки по Python HTML Веб-дизайн Ассемблер в среде Windows ActiveX Javascript Общее о Линукс Линукс - подробно Линукс - новое Delphi Паскаль для начинающих Турбопаскаль Новости Партнеры Наши предложения Архив новостей |
65. Выполняйте настройку явно и преднамеренноРезюмеПри разработке шаблона точки настройки должны быть написаны корректно, с особой тщательностью, а также ясно прокомментированы. При использовании шаблона необходимо четко знать, как именно следует настроить шаблон для работы с вашим типом, и выполнить соответствующие действия. ОбсуждениеРаспространенная ловушка при написании библиотек шаблонов заключается в наличии непреднамеренных точек настройки, т.е. точек в вашем шаблоне, где может выполняться поиск пользовательского кода и его использование, но при написании такие действия вами не подразумевались. Попасть в такую ловушку очень легко — достаточно просто вызвать другую функцию или оператор обычным путем (без полной его квалификации), и если окажется, что один из его аргументов имеет тип параметра шаблона (или связанный с ним), то будет начат поиск такого кода, зависящий от аргумента. Примеров тому множество; в частности, см. рекомендацию 58. Поэтому лучше использовать такие точки преднамеренно. Следует знать три основных пути обеспечения точек настройки в шаблоне, решить, какой именно способ вы хотите использовать в данном месте шаблона, и корректно его закодировать. Затем проверьте, не осталось ли в вашем коде случайных точек настройки там, где вы не предполагали их наличие. Первый способ создания точки настройки — обычный "неявный интерфейс" (см. рекомендацию 64), когда ваш шаблон просто рассчитывает на то, что тип имеет соответствующий член с данным именем: // Вариант 1. Создание точки настройки путем требования от // типа T "foo-совместимости", т.е. наличия функции-члена с // данным именем, сигнатурой и семантикой template<typename T> void Sample1( T t ) { t.foo(); // foo - точка настройки typename T::value_type x; // Еще один пример: создание } // точки настройки для поиска // типа (обычно создается посредством typedef) Для реализации первого варианта автор Sample1 должен выполнить следующие действия.
Второй вариант представляет собой использование метода "неявного интерфейса", но с функциями, не являющимися членами, поиск которых выполняется с использованием ADL3 (т.е. ожидается, что данная функция находится в пространстве имен типа, для которого выполняется инстанцирование шаблона). Именно эта ситуация и явилась основной побудительной причиной для введения ADL (см. рекомендацию 57). Ваш шаблон рассчитывает на то, что для используемого типа имеется подходящая функция с заданным именем: // Вариант 2: Создание точки настройки путем требования от // типа T "foo-совместимости", т.е. наличия функции, не // являющейся членом с данным именем, сигнатурой и // семантикой, поиск которой выполняется посредством ADL. // (Это единственный вариант, при котором не требуется поиск // самого типа T.) template<typename T> void Sample2( T t ) { foo( t ); // foo - точка настройки cout << t; // Еще один пример - operator<< с записью в } // виде оператора представляет собой такую же // точку настройки Для реализации варианта 2 автор Sample2 должен выполнить следующие действия.
Варианты 1 и 2 имеют одинаковые преимущества и применимость: пользователь может один раз написать соответствующую функцию настройки для своего типа и разместить ее там, где ее смогут найти и шаблоны других библиотек. Тем самым пользователь избегает необходимости писать множество мелких адаптеров для каждой библиотеки отдельно. Недостаток же заключается в том, что соответствующая семантика должна быть достаточно широко применима и иметь смысл для всех такого рода потенциальных применений (заметим, что в частности в эту категорию попадают операторы, что является еще одной причиной для рекомендации 26). Третий вариант заключается в использовании специализации, когда ваш шаблон полагается на то, что пользовательский тип специализирует (при необходимости) некоторый иной предоставленный вами шаблон класса. // Вариант 3: Создание точки настройки путем требования от // типа T "foo-совместимости" путем специализации шаблона // SampleTraits<> с предоставлением (обычно статической) // функции с данным именем, сигнатурой и семантикой. template<typename T> void Samplе3( T t ) { s3Traits<T>::foo( t ); // S3Traits<>::foo - // точка настройки typename S3Traits<T>::value_type x; // Другой пример - } // точка настройки для поиска типа (обычно // создается посредством typedef) В этом варианте пользователь пишет адаптер, который гарантирует изолированность кода настройки для данной библиотеки в пределах этой библиотеки. Соответствующий недостаток заключается в том, что это может оказаться слишком громоздким решением; если несколько библиотек шаблонов требуют одну и ту же общую функциональность, пользователь должен будет писать несколько адаптеров, по одному для каждой библиотеки. Для реализации этой версии автор Samplе3 должен выполнить следующие действия.
При использовании любого из перечисленных вариантов следует также четко документировать семантику, требуемую от f оо, в особенности все существенные действия (постусловия), которые должна гарантировать эта функция, и семантику сбоев (что именно происходит при сбое и каким образом должно осуществляться оповещение об ошибках). Если точка настройки должна действовать и для встроенных типов, используйте варианты 2 и 3. Варианты 1 и 2 следует предпочесть для тех общих операций, которые являются предоставляемыми типом сервисами. Для принятия данного решения попробуйте ответить на следующие вопросы: могут ли другие библиотеки шаблонов использовать данную возможность? является ли рассматриваемая семантика приемлемой для данного имени в общем случае? Если вы положительно ответили на эти вопросы, то, вероятно, вам действительно следует предпочесть один из этих вариантов. Вариант 3 лучше использовать для менее общих операций, смысл которых может варьироваться. В таком случае в другом пространстве имен без каких-либо коллизий вы сможете придать тому же имени иной смысл. Шаблон, в котором имеется несколько точек настройки, для каждой из них может выбрать свою стратегию, в наибольшей мере приемлемую в данном месте. Главное, что вы должны осознанно, с пониманием выбирать стратегию для каждой точки настройки, документировать требования к настройке (включая ожидаемые постусловия и семантику ошибок) и корректно реализовать выбранную вами стратегию. Для того чтобы избежать непреднамеренных точек настройки, следует придерживаться следующих правил.
шаблона (например, Т в случае template<typename T>class C:T{};) или от типа, который построен с использованием одного из параметров шаблона (например, Х<Т> в случае template<typename T>class C:X<T>{};). Коротко говоря, при обращении к любому члену зависимого базового класса необходимо всегда явно квалифицировать имя с использованием имени базового класса или при помощи this->. Этот способ можно рассматривать просто как некую магию, которая заставляет все компиляторы делать именно то, что вы от них хотите. template<typename T> class С : Х<Т> { typename Х<Т>::SomeType s; // использование вложенного // типа (или синонима // typedef) из базового // класса public: void f() { X<T>::baz(); // Вызов функции-члена // базового класса this->baz(); // Альтернативный способ }; Стандартная библиотека C++ в основном отдает предпочтение варианту 2 (например, ostream_iterator ищет оператор operator<<, a accumulate ищет оператор operator+ в пространстве имен вашего типа). В некоторых местах стандартная библиотека использует также вариант 3 (например, iterator_traits, char_traits) в основном потому, что эти классы свойств должны быть специализируемы для встроенных типов. Заметим, что, к сожалению, стандартная библиотека C++ не всегда четко определяет точки настройки некоторых алгоритмов. Например, она ясно говорит о том, что трехпараметрическая версия accumulate должна вызывать пользовательский оператор operator+ с использованием второго варианта. Однако она не говорит, должен ли алгоритм sort вызывать пользовательскую функцию swap (обеспечивая таким образом преднамеренную точку настройки с использованием варианта 2), может ли он использовать пользовательскую функцию swap, и вызывает ли он функцию swap вообще; на сегодняшний день некоторые реализации sort используют пользовательскую функцию swap, в то время как другие реализации этого не делают. Важность рассматриваемой рекомендации была осознана совсем недавно, и сейчас комитет по стандартизации исправляет ситуацию, устраняя такие нечеткости из стандарта. Не повторяйте такие ошибки. (См. также рекомендацию 66.) |
|
Библиотека программиста. 2009. |
|