Программирование >>  Включение нужных заголовков 

1 ... 55 56 57 [ 58 ] 59 60 61 ... 71


STL отвергают абсолютно нормальный код - это связано с недоработками то ли компилятора, то ли библиотеки, то ли и того и другого. Например, одна распространенная платформа STL отвергает следующий (вполне допустимый) фрагмент, выводящий в cout длину всех строк в множестве:

set<string> s;

transformCs.beginO. s.endO.

ostreamJterator<string: :size type>(cout. \n ). mem fun ref(&string::size));

Проблема возникает из-за ошибки в работе с константными функциями классов (такими как string:: size) в этой конкретной платформе STL. Обходное решение заключается в использовании объекта функции:

struct StringSize: public unary function<string,string::si2e type> { См. совет 40

string::si2e type operatorO (const strings s) const {

return S.sizeO:

transformCs.beginO, s.endO,

ostream iterator<string::size type>(cout, \n ), StringSizeO):

Существуют и другие обходные решения, но приведенный фрагмент хорош не только тем, что он компилируется на всех известных мне платформах STL. Он также делает возможной подстановку вызова string::size, что почти наверняка невозможно в предыдущем фрагменте с передачей niem fun ref (Sstri ng:: si ze). Иначе говоря, определение класса функтора StringSize не только обходит недоработки компилятора, но и может улучшить быстродействие программы.

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

tempiate<typename FPType> Вычисление среднего

FPType averageCFPType vail. FPType val2) арифметического двух { вещественных чисел

return (vail + val2)/2:

tempiate<typename Inputlterl, typename Inputlter2>

void writeAveragesCInputlter beginl, Вычислить попарные

Inputlter endl, средние значения

Inputlter begin2. двух серий элементов

ostreamS s) в потоке



transform( beginl.endl,begiп2,

ostream iterator<typename iterator traits<InputIterl>::value type>(s, \n ). average<typenaiiie iterator traits<lnputlterl>: :va1ue type> Ошибка?

Многие компиляторы принимают этот код, но по Стандарту С++ он считается недопустимым. Дело в том, что теоретически может существовать другой шаблон функции с именем average, вызываемый с одним параметром-типом. В этом случае выражение average<typenaiiie iterator traits<InputIterl>:: val ue type> становится неоднозначным, поскольку непонятно, какой шаблон в нем упоминается. В конкретном примере неоднозначность отсутствует, но некоторые компиляторы на вполне законном основании все равно отвергают этот код. Решение основано на использовании объекта функции:

tempiate<typename FPType> struct Average:

public binary function<FPType,FPType.FPType>{ См. совет 40

FPType operatorO(FPType vail, FPType val2) const

return average(vall,val2);

tempiate<typenanie Inputlter. typename Inputlter2> void writeAveragesCInputlterl beginl, Inputlterl endl.

Inputlter2 begin2, ostreamS s)

transformC beginl.endl,begin2.

ostreamjterator<typename iterator traits<InputIterl>::va 1 ue type>(s. \n ), Average<typenaiiie iterator traits<InputIterl>: :value typeO

Новая версия должна приниматься любым компилятором. Более того, вызовы Average::operatorO внутри transform допускают подстановку кода, что не относится к экземплярам приведенного выше шаблона average, поскольку average является шаблоном функции, а не объекта функции.

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

Совет 47. Избегайте нечитаемого кода

Допустим, имеется вектор vector<i nt>. Из этого вектора требуется удалить все элементы, значение которых меньше х, но оставить элементы, предшествующие



последнему вхождению значения, не меньшего у. В голову мгновенно приходит следующее решение:

vector<int> v: int х,у;

V.eraseC

renrave i f(fi nd i f(v.rbegi n().v.rendC).

bind2nd(greater equal<int>().y)).base(),

V.endO.

bind2nd(less<int>(),x)).

V.endO):

Всего одна команда, и задача решена. Все просто и прямолинейно. Никаких проблем. Правда?

Не будем торопиться с выводами. Считаете ли вы приведенный код логичным и удобным в сопровождении? Нет! - воскликнет большинство программистов С++ с ужасом и отвращением. Да! - скажут считанные единицы с явным удовольствием. В этом и заключается проблема. То, что один программист считает выразительной простотой, другому представляется адским наваждением.

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

V.fl(f2(f3(v.f40.v.f5().f6(f7().y)).f8().v.f90.f6(fl0(),x)).v.f9()):

Такая запись выглядит неестественно усложненной, поскольку из нее убраны отступы, присутствующие в исходном примере. Можно уверенно сказать, что большинство программистов С++ сочтет, что двенадцать вызовов десяти разных функций в одной команде - это перебор. Но программисты с опытом работы на функциональных языках типа Scheme могут считать иначе. По своему опыту могу сказать, что почти все программисты, которые просматривали этот фрагмент без малейших признаков удивления, имели основательный опыт программирования на функциональных языках. У большинства программистов С++ такой опыт отсутствует, так что если ваши коллеги не привыкли к многоуровневым вложениям вызовов функций, конструкции вроде предыдущего вызова erase будут приводить их в замешательство.

Второй недостаток приведенного кода заключается в том, что для его понимания нужно хорошо знать STL. В нем встречаются менее распространенные i f-формы алгоритмов f i nd и remove, обратные итераторы (см. совет 26), преобразования reversei terator в iterator (см. совет 28), bind2nd и анонимные объекты функций, а также идиома erase-remove (см. совет 32). Опытный программист STL разберется в этой комбинации без особого труда, но гораздо больше будет таких, кто надолго задумается над ней. Если ваши коллеги далеко продвинулись в изучении STL, присутствие erase, removei f, f i ndi f, base и bi nd2nd в одной команде вполне допустимо, но если вы хотите, чтобы ваша программа была понятна программисту С++ со средним уровнем подготовки, я рекомендую разделить эту команду на несколько фрагментов.



1 ... 55 56 57 [ 58 ] 59 60 61 ... 71

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