Анимация
JavaScript
|
Главная Библионтека размещающим new (placement new), и вместо выделения памяти для нового объекта он просто помещает его в область памяти, на которую указывает р. Любой объект, создаваемый таким образом, в общем случае должен быть уничтожен явным вызовом деструктора (как показано в задаче 6, вопрос 1), а не путем использования оператора delete. Итак, зачем же нужны два параметра шаблона? Неужели одного недостаточно для того, чтобы создать копию объекта value? Например, если шаблон construct имеет только один параметр, вам может потребоваться явно указать тип этого параметра при копировании объекта другого типа. Пример 5-2: шаблон construct с меньшими функциональными возможностями, и пояснение, почему они меньше. template <с1ass т1> void constructC Tl* р, const Т1& value ) { new (p) Tl(value); Считаем, что и pi, и p2 указывают на нетипизированную область памяти. void f( double* pi, Base* p2 ) { Base b; Derived d; constructC pi, 2.718 ); OK constructC p2, b ); OK constructC pi, 42 ); Ошибка: Tl - double или int? construct<double>C pi, 42 ); OK constructC p2, d ); Ошибка: Tl - Base или Derived? construct<Base>C p2, d ); OK Причина, no которой два вызова помечены как ошибочные, - в неоднозначности. Компилятору недостаточ1ю имеющейся информации для того, чтобы вывести аргумент шаблона, поэтому для корректности кода следует указывать этот аргумент явным образом. Можем ли мы позволить программисту без каких-либо предупреждений построить объект double из целого значения? Вероятно, в худшем случае это повлечет лишь потерю точности. Можем ли мы позволить построить объект типа Base из объекта Derived? Пожалуй, да. Если это допустимо, то произойдет срезка объекта Derived (но это может б!з1ть сделано намеренно). Итак, если мы хотим позволить программисту выполнять описанные действия без явного указания типов, то нам нужна исходная версия шаблона с двумя независимыми параметрами. Задача 6. Красота обобщенности. Часть 2: Достаточно ли универсальности? Сложность: 7 Насколько универсальной в действительности является обобщенная функция? Ответ может зависеть как от ее реализации, так и от ее интерфейса, а отлично разработанный интерфейс может быть сведен на нет простыми (и трудными в обнаружении) ошибками программирования. Вопрос для профессионала 1. В приведенной функции есть одна малозаметная ловушка, связанная с обобщенностью. В чем она состоит и как лучше всего се исправить? template <с1ass т> voi d destroy С т* р ) { р->~т(); template <с1ass Fwdlter> void destroyC pwdlter first, Fwdlter last ) { whileC fi rst != last ) { destroyC fi rst ); ++fi rst; 2. В чем заключается семантика приведенной далее функции, включая Т1)ебования к Т. Можно ли снять какие-либо из этих требований? Если да, то продемонстрируйте, как именно, и укажите достоинства и недостатки соответствующего решения. template <с1ass т> void swapC т& а, т& b ) { Т temp(a) ; а = b; b = temp; Решение 1. в приведенной функции есть одна малозаметная ловушка, связанная с обобщенностью. В чем она состоит и как лучше всего ее исправить? template <с1ass Т> void destroyC т* р ) { Р->~ТС); template <с1ass Fwdlter> void destroyC Fwdlter first, Fwditer last ) { whileC first != last ) { destroyC first ); ++fi rst; Шаблон функции destroy предназначен для уничтожения диапазона объектов. Первая версия получает один указатель и вызывает деструктор указываемого объекта. Вторая версия получает диапазон итераторов и итеративно уничтожает объекты в указанном диапазоне. Здесь есть одна тонкая ловушка, которая не проявлялась ни в одном примере при первоначальном появлении этого кода в книге [SiitterOO]. Небольшая проблема состоит в том, что функция с двумя параметрами destroyCFwdlter, Fwdlter) может получать любой обобщенный итератор, но при работе она вызывает функцию с 0Д1И1М аргументом destroyCx*), передавая ей один из итераторов. Это автоматически приводит к тому требованию, что итератор Fwditer должен быть обычным указателем! А это означает ненужную потерю общности. У Рекомендация Запомните, что указатели (в массив) всегда являются итераторами, но итераторы не всегда представляют собой указатели. Это также означает, что вы можете получить весьма туманные сообщения об ошибках при попытке скомпилировать код, который пытается осуществить вызов de-stroyCFwdlter, Fwditer) с итераторами, не являющимися указателями, поскольку ошибка будет диагностироваться в строке destroy(fi rst). Выводимые при этом сообщения об ошибках, скажем прямо, особой ясности не прибавляют. Посмотрите, как они выглядят у одного из pacnpocTpaHehHHbix компиляторов. void cdecl destroy(temp"late parameter -1, template-parameter 1) : expects 2 arguments - 1 provided void cdecl destroy(tempiate-parameter-l *) : could not deduce template argument for tempiate-parameter-l * from [использованный тип итератора] Это не худшие из виденных мною сообщений об ошибках, и в принципе не так сложно понять, что именно они обозначают. Первое сообщение указывает, что компилятор пытался разрешить вызов destroyCfi rst); как вызов версии destroy с двумя аргументами. Второе говорит о попытке вызова версии с одним параметром. Обе попытки оказались неудачными, каждая по своей причине. Версию с двумя аргументами устраивает тип итератора, но ей нужны два параметра, а не один. Версия же с одним параметром требует, чтобы он обязательно был указателем. Не везет! В действительности, мы вряд ли когда-то будем использовать функцию destroy с чем-либо кроме указателей в силу се предназначения - превратить объекты в обычную память. Тем не менее, одно небольшое изменение - и Fwditer может быть итератором произвольного типа, а не только указателем. Так почему бы не сделать его? Все, что надо сделать, - это в версии с дву.мя параметрами заменить вызов destroyC first ); вызовом destroyC &*fi rst ); Такая замена будет работать практически всегда. Здесь мы разыменовываем итератор для того, чтобы получить непосредственную ссылку на содержащийся в контейнере объект, а затем получаем его адрес, что дает нам требуемый указатель. В соогвстст-вии со стандартом, все итераторы должны иметь оператор *, который возвращает истинную ссылк> т&. Это одна из причин, по которой стандартом С++- не поддерживаются прокси-контейнеры (дополнительную информацию по этому вопросу можно найти в книге [Sn!ter02]. (Возможны, хотя и крайне редки, ситуации, когда вызов destroy(&"f i rst); будет работать некорректно: как указал хитроумный читатель Билл Вейд (Bill Wade), этот метод неприменим, если в т перегружен оператор &, который возвраиыст нечто вместо адреса объекта. Но это уже патология, и оправдания такому дизайну я просто не в состоянии придумать.) В чем же мораль этой истории? Не забывайте о возможных нарушениях универсальности при реализации одной обобщенной функции с использованием другой. В нашем случае это вьшилось в то, что версия функции с двумя параметрами оказалась не Задача 1.13 в издании на русском языке. -- Прим. ред. 46 Обобщенное программирование и стандартная библиотека С++ 0 1 2 3 4 5 6 7 8 9 [ 10 ] 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |