|
Программирование >> Обобщенные обратные вызовы
Совершенно ясно, что эти функции обязаны быть членами - ведь просто невозможно каким-либо иным способом написать конструктор, деструктор, оператор присваивания или оператор []! Итак, с 12 функциями из обшего списка мы определились, осталось 91... Операции, которые следует сделать членами 2. Каким из функций-членов std::string следует быть членами и почему? Каким операциям требуется доступ ко внутренним данным, так что если делать их не членами, то необходимо будет сделать их друзьями? Это достаточная причина, чтобы реализовать такие операции как члены класса. Их список приведен далее. Некоторые из перечисленных операций обеспечивают косвенный доступ (например, begin) или изменение (например, reserve) внутреннего состояния строки: begin (2) end (2) rbegin (2) rend (2) si ze max si ze capacity reserve swap c str data get anocator Эти функции должны быть членами не только потому, что они тесно связаны с типом basic string. но еще и пото.му что они образуют открытый интерфейс, используемый обычными функциями, не являющимися друзьями класса. Конечно, эти функции можно реализовать и как друзья класса, но зачем? (В действительности есть одна причина, по которой вы можете предпочесть написать также и функции, не являющиеся ни членами, ни друзьями, которые просто вызывают соответствующие члены, а именно - для единообразия интерфейса, мы коснемся этого вопроса немного позже.) Я бы добавил в этот список в качестве фундаментальных следующие сфоковые операции: insert (1 - версия с тремя параметрами); erase (1 - версия iter, iter ); replace (2 - версия iter, iter, num, char и шаблонная версия). Мы еще вернемся к вопросу insert, erase и replace немного позже. Для replace, в частности, важно выбрать в качестве членов наиболее гибкие и фундаментальные версии этой операции. Спорные операции, которые могут не быть ни членами, ни друзьями 3. Покажите, почему функции-члены std::string at, clear, empty и length могут быть реализованы как не члены и не друзья без потери обобщенности, удобства использования и без влияния на остальной интерфейс std::string. Сначала позвольте мне указать, что все перечисленные функции имеют одно общее фундаментальное свойство - все они легко и просто (и эффективно) могут быть переделаны в виде обычных функций, не являющихся друзьями. at (2) clear empty length Конечно, их легко сделать обычными функциями, не связанными дружественными отношениями с классом. Но у этих функций есть еще одно общее фундаментальное свойство, а именно то, что все они упоминаются в контейнерах стандартной библиотеки как функции-члены. Минутку! - слышу я некоторых поклонников стандарта. - Не так быстро! Вы что, не знаете, что basic string разработан таким образом, чтобы соответствовать требованиям к контейнерам, предъявляемым стандартом С++, а эти требования гласят, что некоторые функции должны быть членами? Так что не вводите читателей в заблуждение! Эти функции являются членами, хотите вы того или нет, и с этим ничего нельзя поделать! Да, конечно, это так, но давайте отвлечемся от эго го замечания для того, чтобы беспрепятственно продолжить изучение поднятого в задаче вопроса, и будем считать, что этого требования просто нет. Расе м атри вае мы й мною вопрос не имеет отношения к тому, что говорят требования к контейнерам. Меня интересует другое - какие функции могут без потери эффективности быть сделаны обычными функциями, не являющимися друзьями класса, и дает ли это какие-то дополнительные преимущества. Если такие преимущества существуют, то почему бы не усо верш с н ство вать сами требования? Рассмофим функцию empty. Можем ли мы реализовать ее как обычную функцию, не являющуюся другом класса? Конечно. Стандарт требует, чтобы функция basi c string: : empty вела себя следующим образом [С++03, §21.3.3/14]: Возвращаемое значение: size О == О Значит, легко записать эту функцию как обычную, не являющуюся другом класса, без потери эффективности. template<class charT, class traits, class Allocatoo bool empty(const basic string<charT, traits, Allocator>& s) { return s.sizeO == 0; Конечно, если у нас нет функции size, то реализация empty как обычной функции, не являющейся другом класса, невозможна. Альтернативный (и в некоторых случаях более надежный) способ реализации функции empty выглядит следующим образом. tempiate<class charT, class traits, class Allocator> bool empty(const basic string<charT, traits, Allocator>& s) { return s.beginO == s.end (); Достижима и большая степень обобщенности. tempiate<typename т> bool empty( const т& t ) { return t.beginO == t.end(); Эта окончательная версия никак не связана со строками; все, что она требует от своего аргумента, -- это наличие функций begi п и end. Открытые функции-члены класса должны обеспечивать необходимую и достаточную функциональность. Как мы увидим в дальнейшем, этот вопрос еще не раз возникнет при рассмотрении других функций. (Далее в этой задаче я больше не буду записывать шаблоны функций как полностью обобщенные; все они будут связаны с классом basic string. Однако они вполне могут быть обобщены, и мы увидим, что для этого есть масса причин.) Обратите внимание, что хотя мы можем сделать size членом и реализовать функ-цию-не член empty с ее помощью, мы не можем сделать обратное. В ряде рассматриваемых здесь случаев имеется группа взаимосвязанных функций, некоторые из которых являются членами, а остальные - обычными функциями, которые реализованы с использованием упомянутых функций-членов. Какие именно функции должны быть членами? Мой совет - выбрать наиболее гибкие функции, не приводящие к потере эффективности, которые обеспечат нам гибкий фундамент, на котором можно будет легко построить все остальные функции. В нашем случае мы выбрали si ze в качестве функции-члена, поскольку ее результат всегда может быть кэширован (стандарт поощряет применение кэширования при реализации, поскольку функция size должна выполняться за постоянное время), и в этом случае реализация empty посредством функции size не менее эффективна, чем любой способ ее реализации с использованием полного непосредственного доступа ко внутренним данным класса. А что можно сказать о следующей функции из условия задачи - функции at? К ней применимы те же рассуждения. Как для const, так и для ие-const версии стандарт требует следующего: Генерация исключений: out of. range, если pos>= sizeQ. Возвращаемое значение: operator[](pos). Эту функцию также легко реализовать в виде обычной функции, не являющейся другом класса. Каждая из версий представляет собой двухстрочный шаблон функции, хотя и синтаксически громоздкий из-за использования всех этих параметров шаблона и имен вложенных типов. tempiate<class charT, class traits, class Allocator> typename basic stri ng<charT,traits,Al1ocator>::const reference at(const basic stri ng<charT,traits,All ocator>&s, typename basic string<charT,traits, Allocator>::size type pos) if(pos >= S.sizeO) throw out of range( dont do that ); return s[pos]; template<class charT, class traits, class Allocator> typename basic string<charT, traits, Al1ocator>::reference at(basic string<charT, traits, Allocator>& s, typename basic string<charT, traits, Allocator>::size type pos) if (pos >= S.sizeO) throw out of range( l said, dont do that ); return s[pos]; Что касается clear, то это всего лишь е rase (begi п() , end()) - не больше, не меньше. Реализация в виде обычной функции, не являющейся другом класса, - не более чем простенькое упражнение для читателя. Осталось рассмофеть функцию 1 ength. Здесь тоже все просто - так как эта функция просто возвращает тот же результат, что и si ze. Кроме того, обратите внимание, что другие контейнеры не имеют функции-члена length, и в интерфейсе basi с.....string она играет роль чисто строковой функции . Если мы сделаем ее не членом, то такая функция будет применима к любому контейнеру. Эта функция не Обратите внимание, что это заключение не применимо для класса list, поскольку время работы list: : size линейно зависит от количества элеменгов в списке.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |