====== Вступление ====== “Делайте простые вещи просто.” Лари Уолл ===== Что такое 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]].