BOOST C++: библиотека вспомогательных средств - заголовочный файл boost/operators.hpp

Хидер <boost/operators.hpp> содержит несколько наборов шаблонных классов (в пространстве имен boost). Эти шаблоны определяют операторы, видимые в своем пространстве имен, позволяя классам реализовывать минимальный набор фундаментальных операторов.

Содержание

Обоснование

Перегруженные операторы класса обычно присутствуют группами. Если можно написать x + y, то вероятно также желательно реализовать возможность писать x += y. Если можно написать x < y, также желательно иметь возможность писать x > y, x >= y, и x <= y. Более того, если класс не ведет себя неким нестандартным манером, некоторые из этих операторов отношения (сравнения) могут быть выражены в терминах других операторов  (например x >= y <=> !(x < y)). Повторение этих фрагментов кода для множества классов утомительно и может повлечь ошибки. Шаблоны в boost/operators.hpp могут помочь путем генерации операторов в определенном пространстве видимости на основе других операторов, определенных разработчиком для класса.

Если, к примеру, объявлен такой класс:

class MyInt
    : boost::operators<MyInt>
{
    bool operator<(const MyInt& x) const; 
    bool operator==(const MyInt& x) const;
    MyInt& operator+=(const MyInt& x);    
    MyInt& operator-=(const MyInt& x);    
    MyInt& operator*=(const MyInt& x);    
    MyInt& operator/=(const MyInt& x);    
    MyInt& operator%=(const MyInt& x);    
    MyInt& operator|=(const MyInt& x);    
    MyInt& operator&=(const MyInt& x);    
    MyInt& operator^=(const MyInt& x);    
    MyInt& operator++();    
    MyInt& operator--();    
};

тогда шаблоны operators<> добавляет более дюжины дополнительных операторов, таких как operator>, <=, >=, и (двухаргументный) +. Формы с двумя аргументами шаблонов также генерируются, чтобы обеспечить взаимодействие с другими типами.

Обзор семантики шаблонов

Каждый шаблон оператора делает завершенной концепцию (-ии), которые он описывает, посредством определения перегруженных операторов для своего целевого класса.

Название шаблонного класса оператора отражает концепцию, которую моделирует целевой класс.

Обычно целевой класс использует конкретизацию шаблонного класса оператора как базовый класс. Некоторые операторные шаблоны поддерживают альтернативный метод.

Концепция может быть составной, то есть может быть комбинацией других, более простых концепций.

Большинство операторных шаблонных классов требуют, чтобы целевые классы поддерживали операции, относящиеся к предоставляемым шаблоном операторам. В соответствии с широко используемыми рекомендациями по стилю программирования, целевой класс обычно должен предоставить присваивающий дополнительный оператор к "главному оператору" концепции. К примеру, шаблон addable требует наличия operator+=(T const&) и в свою очередь предоставляет operator+(T const&, T const&).

Польза концепций

Обсуждаемые концепции не обязательно являются концепциями стандартной библиотеки (CopyConstructible, и так далее), хотя некоторые из них могут быть таковыми; мы называем их концепциями с маленькой буквы. В частности, они отличаются от настоящих концепций тем, что не описывают точной семантики нужных им операторов, исключая требования, что (a) семантика операторов, сгруппированных в одной концепции, должна быть целостной (consistent), к примеру результаты вычислений a += b и и и a = a + b должны совпадать), и что (b) типы возвращаемых операторами значений должны следовать семантике типов возврата соответствующих операторов для встроенных типов (к примеру operator< должен возвращать тип, преобразуемый в bool, и T::operator-= должен возвращать тип, преобразуемый в T). Такие "слабые" требования позволяют применять библиотеку операторов в более широком спектре классов из разных областей, то делают библиотеку более полезной.

Использование

Формы шаблонов с двумя аргументами

Общие соображение

Аргументы для бинарных операторов обычно имеют идентичные типы, однако ничто не мешает определять операторы с разными типами аргументов. К примеру, может оказаться необходимым реализовать умножение математического вектора на скаляр. Для этого в библиотеки реализованы двухаргументные формы шаблонов для арифметических типов. При использовании двухаргументной формы шаблона желаемый возвращаемый тип оператора обычно определяет, который из двух типов должен наледовать от шаблона оператора. К примеру, если  результат T + U имеет тип T, тогда именно T (не U) должен наследовать от  addable<T, U>. Шаблоны сравнения (less_than_comparable<T, U>, equality_comparable<T, U>, equivalent<T, U>, и partially_ordered<T, U>) являются исключениями в этом правиле, так как возвращаемый тип для них должен быть bool.

Для компиляторов, не поддерживающих частичную специализацию, двухаргументные формы должны быть указаны с именами, указанными далее, с суффиксом '2'. Одноаргументные формы с суффиксом '1' реализованы для симметрии и чтобы была возможность некоторые применений техники сцепления базовых классов.

Смешанная арифметика

Другим применением двухаргументных форм шаблонов является смешанная арифметика для типов T и U, который может быть преобразован в  T. В этом случае есть два способа извлечь пользу из двухаргументных форм шаблонов: во-первых можно обеспечить приемлемую (respective) сигнатуру для перегрузки операторов, во-вторых можно получить выигрыш в эффективности.

Под приемлемостью сигнатуры для перегрузки операторов понимается следующее. Допустим, что тип U это int, тип T это определенный пользователем неограниченный целочисленный тип, и что double operator-(double, const T&) существует. Если нам нужно вычислить int - T и мы не хотим объявлять T operator-(int, const T&), то компилятор посчитает double operator-(double, const T&) более приемлемым вариантом, нежели T operator-(const T&, const T&), который дал бы результаты, не входившие в намерения пользователя. Чтобы определить полный набор сигнатур операторов, в библиотеке реализованы дополнительные 'левые' формы двухаргументных форм шаблонов (subtractable2_left<T, U>, dividable2_left<T, U>, modable2_left<T, U>), которые объявляют сигнатуры для некоммутативных (non-commutative) операторов, в которых U появляется слева  (operator-(const U&, const T&), operator/(const U&, const T&), operator%(const U&, const T&)).

Относительно эффективности заметьте, что когда используется бинарный оператор с одинаковым типов аргументов для арифметики смешанных типов, аргумент типа U должен быть преобразован в тип T. На практике, однако, часто есть более эффективные реализация для, к примеру, T::operator-=(const U&), которые предотвращают ненужное преобразование типа U в T. Двухаргументные формы шаблонов для арифметических операторов создают дополнительные операторные интерфейсы, которые применяют наиболее эффективные реализации алгоритмов. Однако для 'левых' форм нет выигрыша: они по-прежнему требуют  выполнять преобразование от U к T и имеют реализации, эквивалентные коду, который был бы создан автоматически компилятором, если бы он решил, что бинарные операторы с одинаковым типом являются лучшим соответствием.

Сцепление базовых классов и размер объектов

Каждый операторный шаблонный класс, за исключением арифметических примеров и вспомогательных средств итераторов, имеет дополнительный, хотя и опциональный, шаблонный параметр B. Этот параметр получает значение публично унаследованного (publicly-derived) базового класса конкретизированного шаблона. Это означает, что это должен быть класс. Он может быть использован, чтобы избежать чрезмерного увеличения размера объектов, что обычно ассоциируется с множественным наследованием от нескольких пустых базовых классов (см. замечания для пользователей старых версий). Чтобы обеспечить поддержку групп операторов, следует использовать параметр B для сцепления шаблонов операторов в единую иерархию с единым базовым классом, как это показано в примере использования. Этот прием также используется шаблонами составных операторов для группировки определений операторов. Если цепочка становится слишком длинной для используемого компилятора, следует попробовать заменить некоторые шаблоны операторов единым шаблоном сгруппированных операторов, который сцепляет старые шаблоны вместе; предел длины цепочки относится только к числу шаблонов, связанных напрямую в цепочке, для скрытых в групповых шаблонов это не относится.

Предупреждение: чтобы сделать цепочку с базовым классом, который не является операторным шаблоном Boost при использовании одноаргументной формы операторного шаблона Boost, Вы должны указать имя операторного шаблона с суффиксом '1'. В противном случае библиотека сделает предположение, что Вы намерены определить бинарный оператор, объединив класс, который Вы намерены использовать как базовый класс, и класс, который наследует.

Отдельная, явная конкретизация

Для некоторых компиляторов (к примеру, Borland, GCC) даже простое наследование с одним базовым классом может вызвать возрастание размера объектов в некоторых случаях. Если Вы не определяете шаблонный класс, то можно достичь лучшего значения размера объектов, если вообще отказаться от наследования, и вместо этого явно конкретизировать операторный шаблон таким образом:

    class myclass // lose the inheritance...
    {
        //...
    };

    // explicitly instantiate the operators I need.
    template struct less_than_comparable<myclass>;
    template struct equality_comparable<myclass>;
    template struct incrementable<myclass>;
    template struct decrementable<myclass>;
    template struct addable<myclass,long>;
    template struct subtractable<myclass,long>;

Обратите внимание, что некоторые операторные шаблоны не могут использовать этот прием и должны быть базовым классом для первого типа операторов. Эти шаблоны определяют операторы, которые должны быть функциями-членами, а вышеуказанный прием требует, чтобы операторы были независимыми дружественными (friend) функциями. Вот соответствующие шаблоны:

Как указал Daniel Krügler, рассматриваемый прием нарушает правило 14.6.5/2 Стандарта C++ и поэтому не является платформо-независимым. Причина заключается в том, что возникающие при конкретизации шаблона, к примеру less_than_comparable<myclass> операторы не могут быть найдены в соответствующем пространстве видимости имен согласно правилам в 3.4.2/2 Стандарта, так как myclass не является ассоциированным классом для  less_than_comparable<myclass>. Поэтому используйте данный прием только в случае, если остальные способы не работают.

Платформо-независимость требований

Многие компиляторы (к примеру MSVC 6.3, GCC 2.95.2) не проверяют требования операторных шаблонов до тех пор, пока операции, которые используют эти операторы, действительно используются. Это поведение не следет стандарту. В частности, хотя было бы удобно унаследовать все ваши классы, которым нужны бинарные операторы, от шаблонов operators<> и operators2<>, независимо от того, реализуют ли они все требования этих шаблонов, такая экономия сил  нарушает платформо-независимость кода. Даже если это работает для Вашего компилятора сейчас, это может перестать работать в будущем.

Пример

Этот пример показывает, как некоторые из шаблонов арифметических операторов могут быть использованы в шаблонном классе, описывающем геометрическую точку.

template <class T>
class point    // note: private inheritance is OK here!
    : boost::addable< point<T>          // point + point
    , boost::subtractable< point<T>     // point - point
    , boost::dividable2< point<T>, T    // point / T
    , boost::multipliable2< point<T>, T // point * T, T * point
      > > > >
{
public:
    point(T, T);
    T x() const;
    T y() const;

    point operator+=(const point&);
    // point operator+(point, const point&) automatically
    // generated by addable.

    point operator-=(const point&);
    // point operator-(point, const point&) automatically
    // generated by subtractable.

    point operator*=(T);
    // point operator*(point, const T&) and
    // point operator*(const T&, point) auto-generated
    // by multipliable.

    point operator/=(T);
    // point operator/(point, const T&) auto-generated
    // by dividable.
private:
    T x_;
    T y_;
};

// now use the point<> class:

template <class T>
T length(const point<T> p)
{
    return sqrt(p.x()*p.x() + p.y()*p.y());
}

const point<float> right(0, 1);
const point<float> up(1, 0);
const point<float> pi_over_4 = up + right;
const point<float> pi_over_4_normalized = pi_over_4 / length(pi_over_4);

Арифметические операторы

Шаблоны арифметических операторов облегчают задачу создания пользовательских числовых типов. Для заданного основного набора операторов рассматриваемые шаблоны добавляют операторы, относящиеся к числовым типам. Эти операторы похожи на те, которые есть для стандартных арифметических типов, и могут включать сравнения, сложение, инкремент, логические и побитовые операции, и так далее. Затем, так как большинство числовых типов нуждается более чем в одном из этих операторов, реализовано несколько шаблонов, которые объединяют несколько базовых операторов в одном классе.

Требования к типам, которые используются для конкретизации шаблонов простых операторов, указываются в терминах выражений, которые должны быть корректны, и возвращаемого выражением типа. Составные операторные шаблоны просто перечисляют используемые ими другие шаблоны. Реализованные операции и требования для составных операторных шаблонов могут быть выведены из операций и требований их компонентов.

Простые арифметические операторы

Эти шаблоны являются "простыми" по той причине, что они реализуют операторы на основе единичной операции, которую нужно выполнить с базовым типом. Они имеют дополнительный опциональный параметр шаблона B, который не показан в таблице, используемый для методики сцепления базовых классов.

Обозначения
T: исходный тип операнда U: альтернативный тип операнда
t, t1: объект типа T u: объект типа U
шаблон реализованные операции требования
less_than_comparable<T>
less_than_comparable1<T>
bool operator>(const T&, const T&)
bool operator<=(const T&, const T&)
bool operator>=(const T&, const T&)
t < t1.
возвращается конвертируемое в bool. См. замечания о порядке.
less_than_comparable<T, U>
less_than_comparable2<T, U>
bool operator<=(const T&, const U&)
bool operator>=(const T&, const U&)
bool operator>(const U&, const T&)
bool operator<(const U&, const T&)
bool operator<=(const U&, const T&)
bool operator>=(const U&, const T&)
t < u. t > u.
возвращается конвертируемое в bool. См. замечания о порядке.
equality_comparable<T>
equality_comparable1<T>
bool operator!=(const T&, const T&) t == t1.
возвращается конвертируемое в bool.
equality_comparable<T, U>
equality_comparable2<T, U>
bool operator==(const U&, const T&)
bool operator!=(const U&, const T&)
bool operator!=(const T&, const U&)
t == u.
возвращается конвертируемое в bool.
addable<T>
addable1<T>
T operator+(const T&, const T&) T temp(t); temp += t1.
возвращается конвертируемое в T. См. замечания о симметричности.
addable<T, U>
addable2<T, U>
T operator+(const T&, const U&)
T operator+(const U&, const T& )
T temp(t); temp += u.
возвращается конвертируемое в T. См. замечания о симметричности.
subtractable<T>
subtractable1<T>
T operator-(const T&, const T&) T temp(t); temp -= t1.
возвращается конвертируемое в T. См. замечания о симметричности.
subtractable<T, U>
subtractable2<T, U>
T operator-(const T&, const U&) T temp(t); temp -= u.
возвращается конвертируемое в T. См. замечания о симметричности.
subtractable2_left<T, U> T operator-(const U&, const T&) T temp(u); temp -= t.
возвращается конвертируемое в T.
multipliable<T>
multipliable1<T>
T operator*(const T&, const T&) T temp(t); temp *= t1.
возвращается конвертируемое в T. См. замечания о симметричности.
multipliable<T, U>
multipliable2<T, U>
T operator*(const T&, const U&)
T operator*(const U&, const T&)
T temp(t); temp *= u.
возвращается конвертируемое в T. См. замечания о симметричности.
dividable<T>
dividable1<T>
T operator/(const T&, const T&) T temp(t); temp /= t1.
возвращается конвертируемое в T. См. замечания о симметричности.
dividable<T, U>
dividable2<T, U>
T operator/(const T&, const U&) T temp(t); temp /= u.
возвращается конвертируемое в T. См. замечания о симметричности.
dividable2_left<T, U> T operator/(const U&, const T&) T temp(u); temp /= t.
возвращается конвертируемое в T.
modable<T>
modable1<T>
T operator%(const T&, const T&) T temp(t); temp %= t1.
возвращается конвертируемое в T. См. замечания о симметричности.
modable<T, U>
modable2<T, U>
T operator%(const T&, const U&) T temp(t); temp %= u.
возвращается конвертируемое в T. См. замечания о симметричности.
modable2_left<T, U> T operator%(const U&, const T&) T temp(u); temp %= t.
возвращается конвертируемое в T.
orable<T>
orable1<T>
T operator|(const T&, const T&) T temp(t); temp |= t1.
возвращается конвертируемое в T. См. замечания о симметричности.
orable<T, U>
orable2<T, U>
T operator|(const T&, const U&)
T operator|(const U&, const T&)
T temp(t); temp |= u.
возвращается конвертируемое в to T. См. замечания о симметричности.
andable<T>
andable1<T>
T operator&(const T&, const T&) T temp(t); temp &= t1.
возвращается конвертируемое в T. См. замечания о симметричности.
andable<T, U>
andable2<T, U>
T operator&(const T&, const U&)
T operator&(const U&, const T&)
T temp(t); temp &= u.
возвращается конвертируемое в T. См. замечания о симметричности.
xorable<T>
xorable1<T>
T operator^(const T&, const T&) T temp(t); temp ^= t1.
возвращается конвертируемое в T. См. замечания о симметричности.
xorable<T, U>
xorable2<T, U>
T operator^(const T&, const U&)
T operator^(const U&, const T&)
T temp(t); temp ^= u.
возвращается конвертируемое в T. См. замечания о симметричности.
incrementable<T> T operator++(T&, int) T temp(t); ++t
возвращается конвертируемое в T.
decrementable<T> T operator--(T&, int) T temp(t); --t;
возвращается конвертируемое в T.
left_shiftable<T>
left_shiftable1<T>
T operator<<(const T&, const T&) T temp(t); temp <<= t1.
возвращается конвертируемое в T. См. замечания о симметричности.
left_shiftable<T, U>
left_shiftable2<T, U>
T operator<<(const T&, const U&) T temp(t); temp <<= u.
возвращается конвертируемое в T. См. замечания о симметричности.
right_shiftable<T>
right_shiftable1<T>
T operator>>(const T&, const T&) T temp(t); temp >>= t1.
возвращается конвертируемое в T. См. замечания о симметричности.
right_shiftable<T, U>
right_shiftable2<T, U>
T operator>>(const T&, const U&) T temp(t); temp >>= u.
возвращается конвертируемое в T. См. замечания о симметричности.
equivalent<T>
equivalent1<T>
bool operator==(const T&, const T&) t < t1.
возвращается конвертируемое в bool. См. замечания о порядке.
equivalent<T, U>
equivalent2<T, U>
bool operator==(const T&, const U&) t < u. t > u.
возвращается конвертируемое в bool. См. замечания о порядке.
partially_ordered<T>
partially_ordered1<T>
bool operator>(const T&, const T&)
bool operator<=(const T&, const T&)
bool operator>=(const T&, const T&)
t < t1. t == t1.
возвращается конвертируемое в bool. См. замечания о порядке.
partially_ordered<T, U>
partially_ordered2<T, U>
bool operator<=(const T&, const U&)
bool operator>=(const T&, const U&)
bool operator>(const U&, const T&)
bool operator<(const U&, const T&)
bool operator<=(const U&, const T&)
bool operator>=(const U&, const T&)
t < u. t > u. t == u.
возвращается конвертируемое в bool. См. замечания о порядке.

Замечания о порядке

Шаблоны less_than_comparable<T> и partially_ordered<T> предоставляют одинаковый набор операций. Однако, условия работы  less_than_comparable<T> подразумевают, что все значения типа T могут быть полностью упорядочены. Если это условие не соблюдено (к примеру, есть значения Не-Число (Not-a-Number) для плавающей арифметики IEEE), тогда следует применять шаблон partially_ordered<T>. Шаблон partially_ordered<T> может быть использован для полностью упорядочевыемых типов, однако он не так эффективен, как less_than_comparable<T>. Это правило также применимо для less_than_comparable<T, U> и partially_ordered<T, U> при условии соблюдения требований на упорядочивание всех величин T и U, и для обеих версий equivalent<>. Решение для equivalent<> заключается в написании своего operator== для целевого классаfor the target class.

Замечание о симметричности

Прежде чем говорить и симметрии, необходимо обсудить способы оптимизации, чтобы понять причины различных стилей реализации операторов. Давайте посмотрим на operator+ для некоего класса T в качестве примера:

T operator+( const T& lhs, const T& rhs )
{
   return T( lhs ) += rhs;
}

Это может быть нормальная реализация для operator+, но она не эффективная. Создается неименованная локальная копия lhs, для нее вызывается operator+= и затем она копируется в возвращаемое функцией значение (которое является другим неименованным объектом типа T). Стандарт в общем случае не допускает оптимизацию промежуточного объекта:

3.7.2/2: Продолжительность существования автоматического хранилища

Если именованный автоматический объект имеет операцию инициализации или деструктор с побочными эффектами, то он не должен уничтожаться до конца своего блока, и не должен исключаться в результате оптимизации, даже если этот объект не используется, исключая случаи, когда объект класс или его копия могут быть исключены как описано в 12.8.

Ссылка на 12.8 имеет для нас важное значение:

12.8/15: Копирование объектов классов
...
Для функции, которая возвращает объект класса, в случаях, когда выражение в операторе возврата является именем локального объекта, и неквалифицированный const или volatile тип локального объекта совпадает с возвращаемым функцией типом, допускается пропустить создание временного объекта для хранения возвращаемого функцией значения, даже если конструктор копирования или деструктор имеют побочные эффекты.

Этот способ оптимизации известен как оптимизация именованного возвращаемого значения (named return value optimization - NRVO), что позволяет нам написать такую реализацию operator+:

T operator+( const T& lhs, const T& rhs )
{
   T nrv( lhs );
   nrv += rhs;
   return nrv;
}

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

T operator+( T lhs, const T& rhs )
{
   return lhs += rhs;
}

Разница с первой реализацией заключается в том, что lhs не рассматривается как константная ссылка, используемая для создания копии; вместо этого, lhs передается по значению, так что этот аргумент уже нуждается в копии. Это позволяет применить другую оптимизацию (12.2/2) для некоторых случаев. Рассмотрим выражение a + b + c , где результат a + b не копируется при использовании как lhs, когда прибавляется c. Это более эффективно, чем исходный код, но не так эффективно, как применяющий NRVO компилятор. Для большинства людей последняя реализации предпочтительна для компиляторов, которые не выполняют NRVO, но operator+ теперь имеет другую сигнатуру функции. Кроме того, число создаваемых объектов отличается для выражений (a + b ) + c и a + ( b + c ). Вероятнее всего, что это не создаст для Вас проблем, но если ваш код полагается на сигнатуру функции или на строго симметричное поведение, то следует установить BOOST_FORCE_SYMMETRIC_OPERATORS в вашем конфигурационном файле (user-config). Это заставит использовать допускающую NRVO реализацию даже для компиляторов, которые не реализуют NRVO.

Сгруппированные арифметические операторы

Следующие шаблоны реализуют общераспространенные группы связанных операторов. К примеру, так как тип, который может участвовать в операции сложения, также допускает вычитание, то шаблон additive реализует операторы для обеих этих операций. Шаблоны сгруппированных операторов имеют дополнительный опциональный параметр B, который не показан в таблице, для применения методики сцепления базовых классов.

Обозначения
T: тип исходного операнда U: тип дополнительного операнда
шаблон шаблоны, входящие в состав
totally_ordered<T>
totally_ordered1<t;T>
totally_ordered<T, U>
totally_ordered2<T, U>
additive<T>
additive1<T>
additive<T, U>
additive2<T, U>
multiplicative<T>
multiplicative1<T>
multiplicative<T, U>
multiplicative2<T, U>
integer_multiplicative<T>
integer_multiplicative1<T>
integer_multiplicative<T, U>
integer_multiplicative2<T, U>
arithmetic<T>
arithmetic1<T>
arithmetic<T, U>t;
arithmetic2<T, U>
integer_arithmetic<T>
integer_arithmetic1<T>
integer_arithmetic<T, U>
integer_arithmetic2<T, U>
bitwise<T>
bitwise1<T>
bitwise<T, U>
bitwise2<T, U>
unit_steppable<T>
shiftable<T>
shiftable1<T>
shiftable<T, U>
shiftable2<T, U>
ring_operators<T>
ring_operators1<T>
ring_operators<T, U>
ring_operators2<T, U>
ordered_ring_operators<T>
ordered_ring_operators1<T>
ordered_ring_operators<T, U>
ordered_ring_operators2<T, U>
field_operators<T>
field_operators1<T>
field_operators<T, U>
field_operators2<T, U>
ordered_field_operators<T>
ordered_field_operators1<T>
ordered_field_operators<T, U>
ordered_field_operators2<T, U>
euclidian_ring_operators<T>t;
euclidian_ring_operators1<T>
euclidian_ring_operators<T, U>
euclidian_ring_operators2<T, U>
ordered_euclidian_ring_operators<T>
ordered_euclidian_ring_operators1<T>
ordered_euclidian_ring_operators<T, U>
ordered_euclidian_ring_operators2<T, U>

Примеры шаблонов

Шаблоны классы арифметических операторов operators<> и operators2<> являются примерами классов нерасширяемых группировочных классов операторов. Эти унаследованные из предыдущих версий хидера шаблонные классы не могут быть использованы для сц.

Окончательные шаблонные классы с арифметическими операторами
Обозначения
T: тип исходного операнда U: дополнительный тип операнда
Шаблон Операторные шаблоны, входящие в состав
operators<T>
operators<T, U>
operators2<T, U>

Демонстрационные и тестовые программы для арифметических операторов

Программа operators_test.cpp демонстрирует применение шаблонов арифметических операторов, и может также быть использована для верификации работы. Проверяйте compiler status report, содержащий тестовые результаты для нескольких платформ.

Операторы разыменования и вспомогательные инструменты для итераторов

Шаблоны вспомогательных средств для итераторов облегчают задачу создания пользовательских итераторов. Аналогично арифметическим типам, полный итератор имеет множество "избыточных" операторов, которые могут быть реализованы в виде базового набора операторов.

Операторы разыменования созданы для реализации вспомогательных средств итераторов, но часто оказываются полезными также вне контекста итераторов. Многие из избыточных итераторных операторов являются арифметическими, поэтому классы вспомогательных средств итераторов содержат много операторов, определенных выше. Фактически, нужно определить только два новых оператора (указатель-на-член operator-> и индексатор operator[])!

Требования к типам, используемым для конкретизации операторов разыменования определены в терминах выражений, которые должны быть корректными, и их возвращаемых типов. Для шаблонов составных операторов перечислены входящие в их состав компоненты, которые должны поддерживаться конкретизирующим типом, и возможно задаются другие требования.

Операторы разыменования

Все шаблоны операторов разыменования в таблице получают опциональный параметр шаблона (не показан) для использованная в сцеплении базовых классов.

Обозначения
T: тип оператора P: тип указателя
D: difference_type R: тип ссылки
i: объект типа T (итератор) n: объект типы D (индекс)
Шаблон Реализованные операторы Требования
dereferenceable<T, P> P operator->() const (&*i). Возвращаемое значение может быть преобразовано в P.
indexable<T, D, R> R operator[](D n) const *(i + n). Возвращаемое значение имеет тип R.

Сгруппированные операторы итератора

Имеется пять шаблонных классов операторов для итераторов, по одному для каждой категории итераторов. Следующая таблица показывает группы операторов для каждой категории, которые пользовательский итератор может реализовать. Эти шаблонные классы имеют дополнительный опциональный параметр шаблона B, который не показан, для реализации сцепления базовых классов.

Обозначения
T: тип операнда P: тип указателя
D: тип расстояния R: тип ссылки
V: тип значения (после разыменования)
Шаблон Входящие в состав шаблоны
input_iteratable<T, P>
output_iteratable<T>
forward_iteratable<T, P>
bidirectional_iteratable<T, P>
random_access_iteratable<T, P, D, R>

Вспомогательные средства для итераторов

Также имеется пять шаблонных классов с вспомогательными средствами для итераторов, каждый шаблон для отдельной категории итераторов. Эти классы не могут быть использованы для сцепления баозовых классов. Приведенные далее итоговые описания показывают, что эти шаблоны классов реализуют как итераторные операторы из шаблонных классов итераторных операторов, так и определенные через typedef типы для итераторов, необходимые по стандарту C++ (iterator_category, value_type, и так далее).

Обозначения
T: тип операторв P: тип указателя
D: тип расстояния между итераторами R: тип ссылки
V: тип значения (после разыменования) x1, x2: объекты типа T
Шаблон Операции и требования
input_iterator_helper<T, V, D, P, R> поддерживают операторы и требования input_iteratable<T, P>
output_iterator_helper<T> поддерживают операторы и требования output_iteratable<T>

см. также [1], [2].

forward_iterator_helper<T, V, D, P, R> поддерживают операторы и требования forward_iteratable<T, P>
bidirectional_iterator_helper<T, V, D, P, R> поддерживают операторы и требования bidirectional_iteratable<T, P>
random_access_iterator_helper<T, V, D, P, R> поддерживают операторы и требования random_access_iteratable<T, P, D, R>

Чтобы удовлетворить условию RandomAccessIterator, x1 - x2 должен возвращать значение, которое можно преобразовать в D is also required.

Замечания о вспомогательных средствах для итераторов

[1] В отличие от других шаблонов вспомогательных средств для итераторов, output_iterator_helper принимает только 1 параметр шаблона - тип своего целевого класса. Хотя для кого-то это может показаться слишком сильным ограничением, стандарт C++ требует, чтобы типы difference_type и value_type для любого выходного (output) итератора были void (24.3.1 [lib.iterator.traits]), и шаблон output_iterator_helper следует этим требованиям. Также, выходные итераторы по стандарту C++ имеют типы указателя и ссылки (pointer и reference) равными void, также реализован шаблон output_iterator_helper.

[2] Прием self-proxying это наиболее легкий и общий способ реализовать выходные итераторы (см., к примеру, итераторы вставки [24.4.2] и потоковые итераторы [24.5] в стандартной библиотеке), output_iterator_helper следует этой идиоме путем определения операторов operator* и operator++, которые просто возвращают не-константную ссылку на сам итератор. Поддержка идиомы self-proxying позволяет, во многих случаях, свести задачу написания выходного итератора к задаче написания всего двух функций-членов - подходящего конструктора и оператора копирования-присваивания. Вот, к примеру, возможная реализация адаптора boost::function_output_iterator:

template<class UnaryFunction>
struct function_output_iterator
    : boost::output_iterator_helper< function_output_iterator<UnaryFunction> >
{
    explicit function_output_iterator(UnaryFunction const& f = UnaryFunction())
        : func(f) {}

    template<typename T>
    function_output_iterator& operator=(T const& value)
    {
        this->func(value); 
        return *this; 
    }

 private:
    UnaryFunction func;
};

Обратите внимание, что поддержка self-proxying не мешает использовать шаблон output_iterator_helper для облегчения многих других, отличных видов реализаций выходных реализаций. Если целевой тип для output_iterator_helper реализует собственные определения для operator* и/или operator++, тогда эти операторы будут использованы и операторы, реализованные в шаблоне output_iterator_helper, не будут конкретизированы.

Демонстрационная и тестовая программа для итераторов

Программа iterators_test.cpp демонстрирует использование итераторных шаблонов, и может быть применена для верификации их работы. Далее приведена реализация пользовательского итератора, определенного в тестовой программе. Она демонстрирует корректную (хотя и тривиальную) реализацию основных операций, которые должны быть определены для того, чтобы шаблон вспомогательных средств итераторов "заполнил" оставшиеся итераторные операторы.

template <class T, class R, class P>
struct test_iter
  : public boost::random_access_iterator_helper<
     test_iter<T,R,P>, T, std::ptrdiff_t, P, R>
{
  typedef test_iter self;
  typedef R Reference;
  typedef std::ptrdiff_t Distance;

public:
  explicit test_iter(T* i =0);
  test_iter(const self& x);
  self& operator=(const self& x);
  Reference operator*() const;
  self& operator++();
  self& operator--();
  self& operator+=(Distance n);
  self& operator-=(Distance n);
  bool operator==(const self& x) const;
  bool operator<(const self& x) const;
  friend Distance operator-(const self& x, const self& y);
};

Сравните с результатами в compiler status report.

Разработчики

Dave Abrahams - started the library and contributed the arithmetic operators in boost/operators.hpp.

Jeremy Siek - contributed the dereference operators and iterator helpers in boost/operators.hpp. Also contributed iterators_test.cpp.

Aleksey Gurtovoy - contributed the code to support base class chaining while remaining backward-compatible with old versions of the library.

Beman Dawes - contributed operators_test.cpp.

Daryle Walker - contributed classes for the shift operators, equivalence, partial ordering, and arithmetic conversions. Added the grouped operator classes. Added helper classes for input and output iterators.

Helmut Zeisel - contributed the 'left' operators and added some grouped operator classes.

Daniel Frey - contributed the NRVO-friendly and symmetric implementation of arithmetic operators.

Замечание для пользователей старых версий

Изменения интерфейса библиотеки и рекомендованном применении были мотивированы некоторыми возникшими на практике вопросами, описанными ниже. Новая версия библиотеки поддерживает обратную совместимость со старыми версиями (так что ничто не принуждает вас силой менять существующий код), но прежний способ использования устарел. Хотя старое применение было безусловно проще и более интуитивно, чем использование сцепления базовых классов, было обнаружено, что прежняя практика наследования от множества операторных шаблонов могла привести к сильному возрастанию размера объектов по сравнению с тем, что могло бы быть. Большинство современных компиляторов C++ существенно увеличивают размер классов, производимых от нескольких пустых базовых классов, даже если базовые классы не имели состояния. К примеру, размер point<int> из ранее приводившегося примера оказывается размером 12-24 байтов для различных компиляторов платформы Win32, вместо ожидаемых 8 байтов.

Строго говоря, это не являлось ошибкой библиотеки - правила языка позволяют компилятору применять оптимизацию пустого базового класса в данной случае. В принципе, произвольное число пустых базовых классов может быть размещено с одинаковым же смещение, при условии что ни один из них не имеет общего предка (см. раздел 10.5 [class.derived] параграф 5 стандарта C++). Но спецификации языка C++ не обязывают компилятор выполнять эту оптимизацию, и лишь немногие (если вообще есть таковые) современные реализации компиляторов применяют эту оптимизацию в ситуации множественного наследования. Что еще хуже, маловероятно, что данные реализации компиляторов воспримут этот тип оптимизации, так это повлечет проблемы с бинарной совместимостью кода, оттранслированного разными версиями компилятора. Как сказал Matt Austern, "Один из немногих случаев, когда Вы можете воспользоваться такими вещами, это если вы планируете использовать новую архитектуру...". С другой стороны, многие распространенные компиляторы будут применять оптимизацию пустого базового класса для простого (не множественного) наследования.

В условиях важности данного вопроса для пользователей библиотеки  (которая должна быть полезной для написания легких классов типа MyInt или point<>), и учитывая сказанное выше, мы решили изменить интерфейс библиотеки с тем, чтобы увеличение размера объектов можно было бы подавить даже на компиляторах, который поддерживают оптимизацию пустого базового класса только в простейших случаях. Текущий интерфейс библиотеки является результатов данных изменений. Хотя новый способ использования немного более сложен, чем прежний, мы думаем, что большая полезность в реальном мире стоила того. Алексей Гуртовой (Alexy Gurtovoy) написал код, который реализует новый способ применения библиотеки, но в то же время допускает обратную совместимость.


Revised: 03 Dec 2003

Copyright © Beman Dawes, David Abrahams, 1999-2001.

Copyright © Daniel Frey, 2002-2003.

Use, modification, and distribution is subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at www.boost.org/LICENSE_1_0.txtxt)

последняя правка: 27.05.2005

библиотека BOOST C++ http://www.boost.org
перевод Elijah Koziev www.solarix.ru

  © Mental Computing 2010