Анимация
JavaScript
|
Главная Библионтека настолько универсальной в плане использования итераторов, как мы изначально рассчитывали. Более того, в нашей задаче мы сталкиваемся с еше одним препятствием: попытка исправить ситуацию путем замены destroy(fi rst); вызовом dest roy(&fi rst); приводит к новому требованию к типу т. которое заключается в том, что он должен предоставлять оператор & с обычной семантикой, т.е. возвращающий указатель на объект. Обеих ловушек можно избежать, если не использовать одну функцию в реализации другой. Примечание. Я не призываю вас полностью отказаться от реализации шаблонов с использованием шаблонов; но помните о проблемах, которые может вызвать такое взаимодействие. Очевидно, что шаблоны часто могут быть реализованы при помоши других шаблонов. Например, программисты часто специализируют шаблон std: :swap для своих типов, если они знают более .эффективный способ обмена двух значений. И если вы пишете шаблон сортировки, то эта сортировка должна быть реализована с использованием вызова щаблона swap - в противном случае ваш шаблон не сможет воспользоваться оптимизированным способом обмена содержимого объектов. 2. В чем заключается семантика приведенной далее функции, включая требования к т? Можно ли снять какие-либо из этих требований? Если да, то проде.монстрируйте, как именно, и укажите достоинства и недостатки соответствующего решения. Пример 6-2(а) template <class т> void swap С т& а, т& b ) { т temp(a); а = Ь; Ь = temp; Шаблон функции swap просто обменивает два значения с использованием копирующего конструктора и оператора копирующего присваивания. Таким образом, этот шаблон требует, чтобы тип т имел конструктор копирования и оператор копирующего присваивания. Если это все, что вы смогли сказать, - поставьте себе за этот ответ только полбалла. Одной из важнейщих составляющих семантики любой функции является ее безопасность с точки зрения исключений, включая обеспечиваемые сю гарантии безопасности. В данном случае функция swap не является безопасной в смысле исключений, если оператор копирующего присваивания Т может генерировать исключения. В частности, если Т::operators может генерировать исключения, но при этом атомарен ("все или ничего"), то если второе присваивание оказалось некорректным, мы выходим из функции по исключению, но при этом объект а оказывается уже измененным. Если же оператор т; :operator= может генерировать исключения и не является атомарным, то возможна ситуация, когда мы выходим из функции по исключению и при этом оба параметра оказываются измененными (и один из них может не содержать ни одного из двух исходных значений). Следовательно, данный шаблон функции swap должен быть документирован следующим образом. • Если оператор т: :operator= не генерирует исключений, swap гарантирует атомарность операции ("все или ничего"), за исключением побочных действий операций Т (см. также [Sutter99]). • В противном случае, если оператор т: :operator= может генерировать исключение : • если оператор т: :operator= атомарен и осуществляется выход из функции swap по исключению, первый аргумент функции может быть изменен (хотя и не обязательно); • в противном случае, если оператор т: :operator= не атомарен и осуществляется выход из функции swap по исключению, оба аргумента могут быть изменены (хотя и не обязательно), причем один из них может не содержать ни первое, ни второе исходное значение. Существует два способа избавиться от требования, касающиеся типа т и состоящего в том, что т должен иметь оператор присваивания, причем первый способ обеспечивает больщую безопасность исключений. /. Специализация или перегрузка swap. Пусть, например, у нас есть класс MyClass, который использует распространенную идиому не генерирующей исключений функции Swap. Тогда мы може.м специализировать стандартную функцию для MyClass следующим образом. пример 6-2(6): Специализация swap. class MyClass { public: void Swap( MyClassA ) /* throwO */ ; namespace std { templateo void swap<MyClass>(Myc1ass& a, MyClass& b ) { throwO a.Swap( b ); Мы можем также перегрузить стандартную функцию для MyClass следующим образом: пример б-2(в): перегрузка swap. class MyClass { public: void Swap( Myclass& ) /* throwO */ ; ... примечание: не в пространстве имен std. void swapC MyClass& a, MyClass& b ) /* throwO */ { a.SwapC D ); Обычно это неплохая мысль - даже если т имеет оператор присваивания, который обеспечивает корректную работу исходной версии кода! Например, стандартная библиотека перегружает** swap для векторов, так что вызов swap приводит к вызову vector:: swap. Это имеет большой смысл, поскольку обмен vector: ; swap становится более эффективным, если удается избежать копирования данных, содержащихся в векторах. Первичный шаблон в примере 6-2(а) создает новую копию (temp) одного из векторов, а затем выполняет дополнительное копирование из одного вектора в другой и из вектора temp во второй вектор, что приводит к массе операций т и времени работы 0(N), где N - общий размер обмениваемых векторов. Специализированная версия обычно просто выполняет присваивание нескольких указателей и целочисленных объектов, причем в течение постоянного (и обычно пренебрежимо малого) времени. Так что если вы создаете тип, в котором есть операция наподобие swap, имеет смысл специализировать std:: swap (или предоставить свою собственную функцию swap в другом просфанствс имен) для вашего типа. Обычно это оказывается более эффективным способом обмена, чем работающий "в лоб" шаблон std: iswap из стан- Но не "специализирует", так как вы не можете частично специализировать шаблоны функций. См. задачу 7, в которой более подробно рассматриваются шаблоны функций и специализации. дартной библиотеки; к тому же это зачастую дает более безопасную с точки зрения исключений функцию. > Рекомендация Подумайте о специализации std:: swap для ваших типов, если для них возможен более эффективный способ обмена, чем стандартный. 2 Уничтожение и восстановление. Идея этого способа заключается в обмене с использованием копирующего конструктора т вместо оператора копирующего присваивания; конечно, этот способ работает, только если т имеет конструктор копирования. пример 6-2(г): swap без присваивания. template <class Т> void swap С Т& а, т& b ) { if( &а != &b ) { примечание: эта проверка т tempC а ); destroyC &а ); constructC &а, b ); destroyC &b ); constructC &b, temp ); теперь необходима! Начнем с того, что этот метод не подходит, если конструктор копирования т может генерировать исключения, так как при этом мы получим все тс же проблемы, что и в исходной версии, только еще усугубленные. Вполне реальна ситуация, когда объекты не только хранят некоторые промсжуточные значения, но и вообще НС существуют! Если мы знаем, что конструктор копирования гарантированно не генерирует исключений, то данная версия имеет то преимущество, что позволяет работать с типами, которые не имеют оператора присваивания, но могут быть сконструированы путем копирования, а такие типы вовсе не являются редкостью. Впрочем, возможность обмена для таких типов отнюдь не обязательно считается достоинством. Если тип нельзя присваивать, то, вероятно, на то есть существенные причины, например, если он не имеет семантики значения, но имеет константные или ссылочные члены, то механизм, который обеспечивает (или даже навязывает) типу семантику значения, может приводить к неожиданным и некорректным результатам. Что еше хуже, этот подход начинает игру с временем жизни объектов, а это всегда чревато неприятностями. Здесь под "игрой" я подразумеваю, что при этом изменяются не только значения, но и само существование объектов, с которыми работает данная функция. Код, использующий функцию swap из примера 6-2(г), может легко привести к неожиданным результатам, если пользователь забудет о необычной семантике уничтожения. Небольшая рекомендация. Если вы вынуждены играть со временем жизни объектов и знаете, что здесь все в порядке, и вы точно знаете, что конструкторы копирования объектов, с которыми вы работаете, никогда не генерируют исключений, и вы абсолютно убеждены, что навязанная семантика значений для данных объектов в вашем приложении вполне допустима, то тогда (и только тогда) вы можете оправданно использовать описанный подход в конкретных перечисленных ситуациях. Но даже при этом не делайте такую операцию универсальным шаблоном, который может быть случайно использован для любого другого типа, и четко документируйте ее, чтобы никто случайно не воспользовался ею в другой, не столь благоприятной ситуации, потому что в контексте С++ эта методика выглядит очень экзотично. 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 |