Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Саттер Герб. Стандарты программирования на С++. 101 правило и рекомендация - royallib.ru.doc
Скачиваний:
59
Добавлен:
11.03.2016
Размер:
715.24 Кб
Скачать

Примеры

Вот два примера, адаптированных из [Meyers01].

Пример 1. Преобразование deque . После того как было выполнено несколько некорректных итераций из-за недействительных итераторов (например, см. рекомендацию 83), мы пришли к окончательной версии цикла для прибавления 41 к каждому элементу массива данных типа doublе и помещения результата в дек deque<doublе>:

deque<double>::iterator current = d.begin();

for (size_t i =0; i < max; ++i) {

 // Сохраняем current действительным

 current = d.insert(current, data[i] + 41);

 ++current; // Увеличиваем его, когда это

}           // становится безопасным

Вызов алгоритма позволяет легко обойти все ловушки в этом коде:

transform(

 data.begin(), data.end(),    // Копируем элементы

 data inserter(d, d.begin()), // в d с начала контейнера,

 bind2nd(plus<double>(),41)); // добавляя к каждому 41

Впрочем, bind2nd и plus достаточно неудобны. Откровенно говоря, в действительности их мало кто использует, и связано это в первую очередь с плохой удобочитаемостью такого кода (см. рекомендацию 6).

При использовании лямбда-функций, генерирующих для нас функциональные объекты, мы можем написать совсем простой код:

transform(data, data+max, inserter(d,d.begin()), _1 + 41);

Пример 2. Найти первый элемент между x и у . Рассмотрим простой цикл, который выполняет поиск в vector<int> v первого элемента, значение которого находится между x и y. Он вычисляет итератор, который указывает либо на найденный элемент, либо на v.end():

vector<int>::iterator i = v.begin();

for (; i != v.end(); ++i)

 if (*i > x && *i < y) break;

Вызов алгоритма достаточно проблематичен. При отсутствии лямбда-функций у нас есть два варианта — написание собственного функционального объекта или использование стандартных связывателей. Увы, в последнем случае мы не можем обойтись только стандартными связывателями и должны использовать нестандартный (хотя и достаточно распространенный) адаптер compose2, но даже в этом случае код получается совершенно непонятным, так что такой код на практике никто просто не напишет:

vector<int>::iterator i =

 find_if(v.begin(), v.end(),

 compose2(logical_and<bool>(),

 bind2nd(greater<int>(), x), bind2nd(less<int>(), y)));

Другой вариант, а именно — написание собственного функционального объекта — достаточно жизнеспособен. Он достаточно хорошо выглядит в точке вызова, а главный его недостаток— необходимость написания функционального объекта BetweenValues, который визуально удаляет логику из точки вызова:

template<typename T>

class BetweenValues : public unary_function<T, bool> {

public:

 BetweenValues(const T& low, const T& high)

  : low_(low), high_(high) { }

 bool operator()(const T& val) const

  { return val > low_ && val < high_; }

private:

 T low_, high_;

};

vector<int>::iterator i =

 find_if( v.begin(), v.end(), BetweenValues<int>(x, y));

При применении лямбда-функций можно написать просто:

vector<int>::iterator i =

 find_if(v.begin(), v.end(), _1 > x && _1 < y);

Исключения

При использовании функциональных объектов тело цикла оказывается размещено в некотором месте, удаленном от точки вызова, что затрудняет чтение исходного текста. (Использование простых объектов со стандартными и нестандартными связывателями представляется нереалистичным.)

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

Ссылки

[Allison98] §15 • [Austern99] §11-13 • [Boost] Lambda library • [McConnell93] §15 • [Meyers01] §43 • [Musser01] §11 • [Stroustrup00] §6.1.8, §18.5.1 • [Sutter00] §7