Вступление

“Делайте простые вещи просто.”
    Лари Уолл

Что такое 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 <string>
#include <iostream>
#include <boost/foreach.hpp>
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 работать с другими типами смотри раздел Расширение.

Примеры

Ниже приведены несколько примеров, показывающих все способы, которыми Вы можете использовать BOOST_FOREACH.

  • Перечисление в STL контейнерах:
std::list<int> 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<int> 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<std::vector<int> > matrix_int;
BOOST_FOREACH( std::vector<int> & row, matrix_int )
    BOOST_FOREACH( int & i, row )
        ++i;
  • Перечисление элементов в последовательности заданной выражением, возвращающим последовательность по значению (т.е rvalue):
extern std::vector<float> get_vector_float();
BOOST_FOREACH( float f, get_vector_float() )
{
    // Замечание: get_vector_float() вызывается только один раз
}

Перечисление элементов в последовательности, заданной rvalue не работает на некоторых старых компиляторах. Список компиляторов поддерживающих такую возможность см. в разделе Переносимость.

Улучшение 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, Вы можете прочесть статью Conditional Love в The C++ Source.

 
doc/cpp/boost/foreach.txt · Последние изменения: 2010/04/20 20:16 От ashvidrick
 
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki