Программирование >>  Обобщенные обратные вызовы 

1 ... 75 76 77 [ 78 ] 79 80 81 ... 84


слишком полезна, поскольку представляет собой просто синоним size, но зато она хорошо иллюстрирует принцип, который я хотел вам продемонстрировать, а именно - как только алгоритмы становятся фуикциями-не членами, тут же растет их полезность и расширяется область применения.

Резюмируя, давайте рассмотрим преимущества и недостатки, которые мы получаем, переделывая функции-члены наподобие empty, в обычные функции. Начнем с того, что даже если мы можем переписать функцию-член как обычную функцию, не являющуюся другом класса, без потери эффективности, то почему мы должны этим заниматься? Какие реальные или потенциальные преимущества сможем мы при этом получить?

/. Простота. Сделав функции не членами, мы сокращаем код, который должны писать и сопровождать. Мы можем написать функцию empty раз и навсегда. Зачем нам много раз писать разные варианты функции -basi cstri ng:: empty, vector: : empty, list:: empty и так далее, включая написание этой функции для всех новых STL-совместимых контейнеров, которые могут появиться в каких-то библиотеках сторонних производителей или даже в будущих версиях стандарта С++?

Заметим, что у этого преимущества имеются некоторые офаничения. Некоторые из функций, такие как at, не способны обеспечить одинаковое время работы, поскольку сложность функции будет варьироваться в зависимости от используемого контейнера. Так, для тар функция имеет логарифмическое время работы, в то время как для vector время работы функции является константой. Далее, может оказаться так, что не все контейнеры, имеющиеся в настоящее время или те, которые будут разработаны в будущем, будут предоставлять необходимый для работы функции интерфейс. Как видно из приведенного выше примера, функция empty, не являющаяся членом класса, использует функцию-член size и не будет работать с контейнерами, которые эту функцию не поддерживают; кроме того, для контейнеров, которые имеют достаточный, но отличный от фебуемого интерфейс, потребуются специализированные или перефуженные версии функций.

2 Последовательность. Таким образом мы избегаем неоправданных несовмесгамостей между алгоритмами, используемыми в различных контейнерах, а также между алгоритмами, используемыми в функциях-членах и обьиных функциях (некоторые реально существующие несовместимости такого рода между функциями-членами и функциями-не членами указаны в [MeyersO!]). Если требуется настройка поведения функций, то ее можно выполнить с помощью специализации или перефузки шаблона функции.

3. Инкапсуляция. Использование обычных функций повышает степень инкапсуляции (что доказано в [MeyersOO]).

Итак, положительные стороны такого решения нами перечислены. А как насчет отрицательных сторон? Их две, хотя лично мне кажется, что достоинства в данном случае перевешивают.

4. Засорение пространств имен. Поскольку empty - достаточно распространенное имя, размещение его в области видимости пространства имен приводит к риску засорения последнего - разве будут все функции с именем empty иметь одну и ту же семантику? Однако, во-первых, последовательность семантики - Хорошая Вещь, а во-вторых, разрешение перефузки - хорошее противоядие против неоднозначностей, так что засорение пространства имен не такая большая проблема, как многие считали ранее. Действительно, собирая все имена функций в одно место и предоставляя общую их реализацию, мы на самом деле не столько засоряем пространство имен, сколько, как отмечает Мейерс, очищаем сами функции, собирая их в одном месте и тем самым помогая избегать очевидных и необоснованных случаев их несовместимости.

Конечно, такой контейнер не отвечает требованиям, предъявляемым к стандартным контейнерам STL. Однако, возможно, мы захотим работать и с такими, не вполне отвечающими стандарту контейнерами.



5. Последовательность. Если все функции наподобие empty являются функциями-членами, то такое решение наиболее последовательно, поскольку все похожие функции имеют один и тот же синтаксис вызова. Вряд ли приятно запоминать, что надо писать length(str), но str.size(). Этот аргумент может показаться убедительным, но только до тех пор, пока мы не заметим, что достаточно написать для всех имеющихся функций-членов соответствующие версии обычных функций, как непоследовательность оказывается только кажущейся.

В частности, что получится, если мы добавим для функций-членов наподобие size их обычные версии, которые будут просто вызывать соответствующие функции-члены? Это приведет к унификации синтаксиса вызовов. Например, если мы напишем функцию size, которая не будет членом класса, будут одинаково корректны оба варианта вызовов - как size(str), так и str.sizeC), что избавит от необходимости запоминать, какие именно функции являются членами класса. В таком случае везде будет использоваться только синтаксис вызова обычной функции, и мы получим все преимущества простоты, последовательности и инкапсуляции.

Кстати, имеются и другие технические и конструкторские причины для предпочтения синтаксиса обычных функций. Последовательное написание обычных функций, не являющихся членами, существенно облегчает написание шаблонов. Если шаблоны могут для всех типов использовать функции, не являющиеся членами (в отличие от ситуации, когда часть функций является членами, а часть - нет), то они могут избежать использования классов-характеристик (traits) для выяснения, какие именно функции являются членами, а какие - нет, чтобы код корректно работал в обоих случаях (см. более подробные примеры в [Suttert)2]). Другая причина заключается в том, что предоставляется возможность перегружать (бывшие) функции-члены и не члены. Если вы в ужасе отпрянете и будете этому сопротивляться - учтите, что, как показывает опыт, зачастую это Хорошая Идея, и некоторые члены комитета по стандартизации С++ считают, что в новый стандарт С+ + (С++Ох) можно бы было добавить перегрузку функций-членов и не членов. (Эта возможность может быть отвергнута, но некоторые эксперты считают, что в целом это потенциально неплохая идея).

Так что все возражения по поводу последовательности в действителыiости отпадают, поскольку написание всех, где только это возможно, функций как обычных функций, не являющихся членами, приводит только к большей последовательности.

В следующей задаче мы рассмотрим еще несколько операций, и выясним, нельзя ли и их сделать обычными функциями, не являющимися друзьями класса. Некоторые из них в соответствии с требованиями стандарта должны быть членами классов контейнеров, но мы будем рассматривать не вопрос о том, что нам говорит стандарт, а скорее о том, как бы мы разрабатывали эти классы, имея возможность начать все с белого листа.



Задача 39. Ослабленная монолитность.

Часть 3: уменьшение std:: stri ng Сложность: 5

Продолжаем разработку диеты для быстрого похудения std::string... Вопрос для новичка

1. Может ли string:: resize быть функцисй-не членом? Обоснуйте свой ответ. Вопрос для профессионала

2. Проанализируйте следующие функции std: :string и покажите, могут ли они быть преобразованы в обычные фуикции-не члены. Обоснуйте ваш ответ.

а) Присваивание, а также +=/append/push back.

б) insert.

Решение

Операции, которые могут не быть членами

В этой задаче мы увидим, что все перечисленные далее функции .могут быть реализованы как обычные функции, не являющиеся друзьями класса.

resize (2)

assign (6)

+=(3)

append (6)

push back

insert (7 - все, кроме версии с тремя параметрами) Приступим.

resize

1. Может ли string:: resize быть функцией-не членом? Обоснуйте свой ответ. Давайте посмотрим.

void resize(size type n, charT с); void resizeCsize type n);

Можно ли эти функции сделать обычными функциями, не являющимися друзьями класса? Конечно, можно, поскольку их можно реализовать с использованием открытого интерфейса basic string без потери эффективности. В самом деле, спецификации стандарта выражают обе функции resize через другие, которые мы уже рассмат-ривали. Например, реализовать их как обычные функции, не являющиеся друзьями класса, можно следующим образом.

template<class charT, class traits, class Allocatoo void resize(basic string<charT, traits, Allocator>& s, typename Allocator::sizetype n, charT с )

if( n > s.max si2e() ) throw 1ength error( wont fit ); if( n <= s.sizeO ) {

basic string<charT, traits, Allocator> temp(s,0,n);

s.swap( temp );



1 ... 75 76 77 [ 78 ] 79 80 81 ... 84

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика