====== Вступление ======
“Делайте простые вещи просто.”
Лари Уолл
===== Что такое BOOST_FOREACH =====
В C++ написание цикла, работающего с последовательностями - процесс нудный. Мы можем использовать либо итераторы, требующие немалого количества дополнительных действий, либо алгоритм std::for_each и перенести тело цикла в предикат, что не требует дополнительных действий, но заставляет перенести описание логики далеко от места, где она будет использована. В отличии от C++, некоторые языки, например Perl, имеют специальную конструкцию “foreach”, автоматизирующую этот процесс. BOOST_FOREACH как раз и есть такая конструкция для C++. Этот макрос перечисляет элементы последовательности, освобождая нас от необходимости иметь дело непосредственно с итераторами или описывать предикаты.
BOOST_FOREACH разработан для легкого и эффективного использования. Он не производит динамических выделений памяти, не вызывает виртуальные функции, не вызывает функции через указатели на них и не производит вызовов, непрозрачных для оптимизации компилятором. В результате происходит генерирование почти оптимального кода; быстродействие BOOST_FOREACH лишь на несколько процентов ниже быстродействия циклов, написанных «вручную». Кроме того, несмотря на то, что BOOST_FOREACH это макрос, он замечательно предсказуем в своем поведении. Он вычисляет свои аргументы только один раз, не привод к неприятным сюрпризам.
===== Hello, world! =====
Ниже приведена программа, использующая BOOST_FOREACH для циклического перебора содержимого std::string.
#include
#include
#include
int main()
{
std::string hello( "Hello, world!" );
BOOST_FOREACH( char ch, hello )
{
std::cout << ch;
}
return 0;
// Данная программа выводит следующее:
// Hello, world!
}
===== Поддерживаемые типы последовательностей =====
BOOST_FOREACH производит итерации последовательностей. Но что именно он “считает” последовательностью? Так как BOOST_FOREACH написан на основе Boost.Range, он автоматически поддерживает типы, которые Boost.Range воспринимает как последовательности. В частности, BOOST_FOREACH работает с типами, удовлетворяющими концепции «однопроходный диапазон». Так, BOOST_FOREACH может работать с:
* Контейнерами STL
* Массивами
* Строками, заканчивающимися нулем
* Парами (Std::pairs) итераторов (см примечание)
Поддержка контейнеров STL очень широка, все что выглядит как STL контейнер может быть использовано. Если контейнер имеет встроенные типы **iterator** и **const_iterator** и члены-функции **begin()** и **end()**, BOOST_FOREACH автоматически может проводить вычисления с ним. Таким образом, **boost::iterator_range<>** и **boost::sub_range<>** работают с BOOST_FOREACH.
Для того, чтобы узнать, как заставить BOOST_FOREACH работать с другими типами смотри раздел [[doc:cpp:boost:foreach#расширение|Расширение]].
===== Примеры =====
Ниже приведены несколько примеров, показывающих все способы, которыми Вы можете использовать BOOST_FOREACH.
* Перечисление в STL контейнерах:
std::list list_int( /*...*/ );
BOOST_FOREACH( int i, list_int )
{
// do something with i
}
* Перечисление в массивах с ковариантостью (то есть тип итерационной переменной не в точности совпадает с типом элементов в контейнере):
short array_short[] = {1,2,3};
BOOST_FOREACH( int i, array_short )
{
// short автоматически приводится к int
}
* Предварительное объявление переменной цикла и использование break, continue, и return в теле цикла:
std::deque deque_int( /*...*/ );
int i = 0;
BOOST_FOREACH( i, deque_int )
{
if( i == 0 ) return;
if( i == 1 ) continue;
if( i == 2 ) break;
}
* Перечисление элементов в последовательности по ссылке и изменение по ссылке
элементов контейнера:
short array_short[] = { 1, 2, 3 };
BOOST_FOREACH( short & i, array_short )
{
++i;
}
// array_short теперь содержит {2,3,4}
* Перечисление элементов в векторе векторов вложенными циклами BOOST_FOREACH. Обратите внимание, что в этом примере фигурные скобки вокруг тела цикла необязательны:
std::vector > matrix_int;
BOOST_FOREACH( std::vector & row, matrix_int )
BOOST_FOREACH( int & i, row )
++i;
* Перечисление элементов в последовательности заданной выражением, возвращающим последовательность по значению (т.е rvalue):
extern std::vector get_vector_float();
BOOST_FOREACH( float f, get_vector_float() )
{
// Замечание: get_vector_float() вызывается только один раз
}
Перечисление элементов в последовательности, заданной rvalue не работает на некоторых старых компиляторах. Список компиляторов поддерживающих такую возможность см. в разделе [[doc:cpp:boost:foreach#переносимость|Переносимость]].
===== Улучшение BOOST_FOREACH =====
Некоторым людям не нравится название BOOST_FOREACH. Оно слишком длинное. ВСЕ ЗАГЛАВНЫЕ БУКВЫ утомительны для чтения. Возможно, эти претензии справедливы, но BOOST_FOREACH просто следует правилам именования Boost. Хотя, это и не значит, что Вас это должно озадачивать. Если Вы хотите использовать другой идентификатор (например, foreach), Вы можете просто написать:
#define foreach BOOST_FOREACH
Поступайте так, только если вы уверены, что идентификатор, который Вы выбрали, не приведет к конфликту имен в вашем коде.
ПРИМЕЧАНИЕ
Не используйте #define foreach(x,y) BOOST_FOREACH(x,y). Это может привести к проблемам, если аргументы сами являются макросами. Это может привести к дополнительному разворачиванию этих макросов. Вместо этого используйте форму, описанную выше.
====== Расширение ======
Если мы хотим использовать BOOST_FOREACH для перечисления элементов некоторого нового типа контейнера, мы должны "научить" BOOST_FOREACH работать с нашим типом. Так как BOOST_FOREACH реализован с помощью Boost.Range, мы должны расширить Boost.Range для расширения BOOST_FOREACH. Данный раздел детально описывает расширение Boost:Range.
Ниже приведен пример расширения BOOST_FOREACH для перечисления элементов в типе sub_string, который содержит два итератора std::string.
namespace my
{
// sub_string: часть строки, ограниченной парой итераторов
struct sub_string
{
std::string::iterator begin;
std::string::iterator end;
/* ... реализация ... */
};
// Добавить перезагрузку boost_range_begin() и boost_range_end() в пространстве имен,
// в котором расположена sub_string, для того чтобы быть обнаруженным Argument-Dependent Lookup.
inline std::string::iterator boost_range_begin( sub_string & x )
{
return x.begin;
}
inline std::string::iterator boost_range_end( sub_string & x )
{
return x.end;
}
// Так же перегрузим const sub_strings. Обратите внимание что используется
// преобразование из string::iterator в string::const_iterator.
inline std::string::const_iterator boost_range_begin( sub_string const & x )
{
return x.begin;
}
inline std::string::const_iterator boost_range_end( sub_string const & x )
{
return x.end;
}
}
namespace boost
{
// определение rannge_iterator и range_const_iterator в пространстве имен boost
template<>
struct range_iterator< my::sub_string >
{
typedef std::string::iterator type;
};
template<>
struct range_const_iterator< my::sub_string >
{
typedef std::string::const_iterator type;
};
}
Теперь, когда мы научили Boost.Range (и, соответственно, BOOST_FOREACH) работе с нашим типом, мы можем использовать BOOST_FOREACH для перечисления элементов нашего типа sub_string.
my::sub_string substr;
BOOST_FOREACH( char ch, substr )
{
// Ура!
}
Существует несколько проблем с переносимостью, которых мы должны избежать при расширении BOOST_FOREACH. Проверьте раздел переносимость. В частности, если ваш компилятор не поддерживает Argument-Dependent Lookup, раздел "Переносимость" для Boost.Range, предлагающий рекомендуемые решения.
===== BOOST_FOREACH и Non-Copyable типы =====
Для работы с non-copyable типами-последовательностями, нам необходимо указать BOOST_FOREACH, что он не должен пытаться делать копий. Если ваш тип наследуется от **boost::noncopyable**, то никаких действий предпринимать не надо. Если же нет, необходимо конкретизировать **boost::foreach::is_noncopyable<>** шаблон, как показано ниже:
class noncopy_vector
{
// ...
private:
noncopy_vector( noncopy_vector const & ); // non-copyable!
};
namespace boost { namespace foreach
{
template<>
struct is_noncopyable< noncopy_vector >
: mpl::true_
{
};
}}
Другой способ добиться того же эффекта - перегрузить глобальнуюфункцию **boost_foreach_is_noncopyable()**. Преимущество этого способа в том, что его применение сохраняет совместимость со старыми компиляиорами.
// В глобальном нэймспейсе...
inline boost::mpl::true_ *
boost_foreach_is_noncopyable( noncopy_vector *&, boost::foreach::tag )
{
return 0;
}
Примечание. Хотя мы и должны указывать BOOST_FOREACH, что наш тип non-copyable, это вовсе не означает, что BOOST_FOREACH всегда создоет копии нашего типа. Очевидно, что такое поведение непроизводительно, а в некоторых случаях ведет к ошибкам. BOOST_FOREACH достаточно "умен", чтобы поять, когда создавать копии, а когда нет. Признак **is_noncopyable<>** необходим, чтобы избежать копирования в случае когда оно невозможно.
===== Оптимизация BOOST_FOREACH для легковесных оболочек типов-последовательностей =====
На некоторых компиляторах BOOST_FOREACH должен иногда генерировать немного медленный код, чтобы гарантировать корректную обработку последовательностей во временно создаваемых объектах. Он как бы спрашивает себя "Должен ли я делать копию этого объекта" и затем, "Сделал ли я копию или нет?". Для некоторых типов это черезмерно. Представьте себе последовательность, являющуюся простой парой итераторов. Прыжки через горящий обруч(танцы с бубном)для избежания копирования бессмысленны, потому что копирование дешево.
Пара итераторов это пример легковесной оболочки. Она не хранит значения в последовательности, а всего лишь их итераторы. Это значит, что перечисление в копии легковесной оболочки даст те же результаты, что и перечисление с использованием объекта. Для таких типов BOOST_FOREACH предлагает средство, позволяющее нам не беспокоиться о расходах на копирование. Это может привести к более быстрому исполнению цикла. Просто конкретизируйте **boost::foreach::is_lightweight_proxy<>** свойство как указано ниже:
struct sub_string
: boost::iterator_range< std::string::iterator >
{
// ...
};
namespace boost { namespace foreach
{
template<>
struct is_lightweight_proxy< sub_string >
: mpl::true_
{
};
}}
Того же эффекта можно добиться перегрузкой глобальной функции, как указано ниже:
// В глобальном пространстве имен...
inline boost::mpl::true_ *
boost_foreach_is_lightweight_proxy( sub_string *&, boost::foreach::tag )
{
return 0;
}
Этот метод совместим со старыми компиляторами.
====== Переносимость ======
BOOST_FOREACH использует некоторые передовые техники, не поддерживаемые всеми компиляторами.
В зависимости от того, как на сегодняшний день Ваш компилятор совместим с Boost, Вы не можете испольовать BOOST_FOREACH в некоторых сценариях. Так как BOOST_FOREACH использует Boost.Range, он наследует принципы переносимости этой библиотеки. Вы можете прочитать об этом в разделе Boost.Range:Переносимость.
В дополнение к требовниям к компилятору Boost.Range, BOOST_FOREACH выдвигает собственные требования для правильной обработки последовательностей rvalue. (Помните, что rvalue это неименнованный объект, таким образом последовательность rvalue может быть функцией, возвращающей **std::vector<>** по значению.) Компиляторы по-разному обрабатывают rvalues и lvalues. Для охвата всех типов компиляторов BOOST_FOREACH определяет три типа совместимости, описанных в таблице:
^ Уровни совместимости BOOST_FOREACH ^^
^ Уровень ^ Значение ^
|Уровень 0 |__Высший уровень совместимости__. BOOST_FOREACH работает с lvalues, rvalues и const-qualified rvalues.|
|Уровень 1 |__Средний уровень совместимости__. BOOST_FOREACH работает с lvalues and обычными rvalues, но не с const-qualified rvalues. В этом случае определено BOOST_FOREACH_NO_CONST_RVALUE_DETECTION. |
|Уровень 2 |__Низший уровень совместимости__. BOOST_FOREACH работает только с lvalues. В этом случае определено BOOST_FOREACH_NO_RVALUE_DETECTION.|
Ниже приведен список компиляторов, на которых тестировался BOOST_FOREACH и и уровень их совместимости с BOOST_FOREACH.
^Compiler ^Compliance Level^
|Visual C++ 8.0 |Level 0|
|Visual C++ 7.1 |Level 0|
|Visual C++ 7.0 |Level 2|
|Visual C++ 6.0 |Level 2|
|gcc 4.0 |Level 0|
|gcc 3.4 |Level 0|
|gcc 3.3 |Level 0|
|mingw 3.4 |Level 0|
|Intel for Linux 9.0 |Level 0|
|Intel for Windows 9.0 |Level 0|
|Intel for Windows 8.0 |Level 1|
|Intel for Windows 7.0 |Level 2|
|Comeau 4.3.3 |Level 0|
|Borland 5.6.4 |Level 2|
|Metrowerks 9.5 |Level 1|
|Metrowerks 9.4 |Level 1|
|SunPro 5.8 |Level 2|
|qcc 3.3 |Level 0|
|tru64cxx 65 |Level 2|
|tru64cxx 71 |Level 2|
====== История и благодарности ======
===== История =====
Идеи BOOST_FOREACH появились в Microsoft Visual C++ Group на этапе раннего проектирования C++/CLI. Вопрос о добавлении к языку конструкции "foreach" до сих пор открыт. В качестве мысленного эксперимента Энсон Цао (Anson Tsao) разослал пробный код, демонстрирующий, возможность такого решения методами стандартной библиотеки. Этот код был написан на современом тогда диалекте C++/CLI, для которого тогда еще не было компилятора. Я был заинтригован такой возможностью и портировал его код на Managed C++ и довел его до рабочего сотояния. Мы работали вместе для улучшения этой идеи и даже опубликовали статью о ней в ноябрьском выпуске 2003 года CUJ.
После того, как я ушел из Microsoft, я переписал макрос на стандартном C++, исправил некоторые изъяны версии CUJ, и окрестил его BOOST_FOREACH. В октябре 2003 я начал дискуссию о BOOST_FOREACH в Boost developers list, где он был встречен достаточно равнодушно. Я оставил эту задачу до декабря 2004, когда я снова переписал BOOST_FOREACH. Новая версия вычисляла выражение, задающее последовательность, только один раз и правильно обрабатывала lvalue и rvalue выражения, задающие последовательность. Она была написана на основе только что принятой библиотеки Boost.Range с улучшенной переносимостью. Эту версию 12 декабря 2004 я наконец-таки выложил для обозрения. Она была включена в состав Boost 5 мая 2005.
===== Благодарности =====
Спасибо Энсону Цао (Anson Tsao) из Microsoft за то, что он натолкнул меня на этк идею и продемонстрировал возможность ее осуществления. Хотелось бы также поблагодарить Торстена Оттосена (Thorsten Ottosen) за библиотеку Boost.Range на основе которой написана текущая версия BOOST_FOREACH. И, наконец, хотелось бы поблагодарить Рассела Хинда (Russell Hind), Элисдэйр Мередит (Alisdair Meredith) и Стивена Слапета (Stefan Slapeta) за их помощь в портировании для различных компиляторов.
====== Что дальше ======
Для получения более подробной информации о том, как работает BOOST_FOREACH, Вы можете прочесть статью [[http://www.artima.com/cppsource/foreach.html|Conditional Love]] в [[http://www.artima.com/cppsource/|The C++ Source]].