В нижеследующих пунктах описываются вопросы, которые необходимо было рассмотреть в ходе осуществления циркулярного буфера:
Безопасность потока<circular_buffer>такая же, как и безопасность потока контейнеров в большинстве реализаций STL. Это означает, что<circular_buffer>не является полностью безопасным. Безопасность потока гарантируется только в том смысле, что одновременный доступ к отдельным экземплярам<circular_buffer>безопасен, а одновременный доступ к совместному<circular_buffer>безопасен.
Если несколько потоков получают доступ к одному<circular_buffer>, и, по меньшей мере, один из потоков может потенциально записывать, то пользователь несет ответственность за обеспечение взаимного исключения между потоками во время доступа к контейнеру. Взаимное исключение между нитями может быть достигнуто за счет операций обертывания<circular_buffer>с приобретением и выпуском замка. (См. примерный код Bounded Buffer по адресуcircular_buffer_bound_example.cpp)
Операция перезаписи происходит, когда элемент вставляется в полный<circular_buffer>— старый элемент перезаписывается новым. Была дискуссия о том, что именно означает «перезапись элемента» во время формального обзора. Это может быть либо разрушение исходного элемента и последующее строительство нового элемента, либо присвоение нового элемента старому.<circular_buffer>выполняет задание, потому что оно более эффективно.
С точки зрения бизнес-логики хранимого элемента операция разрушения / строительства и назначение обычно означают одно и то же. Однако в очень редких случаях (если таковые имеются) они могут отличаться. Если существует требование о том, чтобы элементы разрушались/конструировались вместо назначения, рассмотрите возможность реализации обертки элемента, которая будет реализовывать оператора назначения, и вместо этого храните обертки. Необходимо отметить, что хранение таких оберток имеет недостаток. Разрушение/конструкция будет вызываться при каждом назначении обертки - не только когда обертка перезаписывается (когда буфер заполнен), но и когда хранимые обертки смещаются (например, в результате вставки в середину контейнера).
Существует несколько вариантов, как справиться, если источник данных производит больше данных, чем может поместиться в буфер фиксированного размера:
- Сообщите источнику данных подождать, пока в буфере не появится место (например, путем исключения переполнения).
- Если самые старые данные являются наиболее важными, игнорируйте новые данные из источника, пока в буфере снова не появится место.
- Если последние данные являются наиболее важными, запишите самые старые данные.
- Пусть производитель несет ответственность за проверку размера буфера перед его записью.
Очевидно, что<circular_buffer>реализует третий вариант. Но менее очевидно, что он не реализует никакой другой вариант, особенно первые два. Может сложиться впечатление, что<circular_buffer>должны реализовать первые три варианта и предложить механизм выбора среди них. Это впечатление неправильное.
<circular_buffer>был разработан и оптимизирован для круговой записи (что означает перезапись самых старых данных при заполнении). Если бы такой механизм управления был включен, это только усложнило бы ситуацию, и использование<circular_buffer>было бы, вероятно, менее простым.
Кроме того, первые два варианта (а также четвертый вариант) не требуют, чтобы буфер был круговым. Если есть необходимость в первом или втором варианте, рассмотрите возможность реализации адаптера, например, std::vector. В этом случае<circular_buffer>не подходит для адаптации, потому что, в отличие от std::vector, он несет накладные расходы за его круговое поведение.
При считывании или удалении элемента из пустого буфера буфер должен быть в состоянии уведомить потребителя данных (например, путем исключения из потока данных), что в нем не хранятся элементы.<circular_buffer>не осуществляет такое поведение по двум причинам:
- Это приведет к повышению производительности.
- Ни один другой контейнер не реализует его таким образом.
Считается ошибкой читать или удалять элемент (например, позвонив<front()>или<pop_back()>) из пустого контейнера и из пустого<circular_buffer>. Потребитель данных должен проверить, не является ли контейнер пустым, прежде чем считывать/удалять его путем тестирования<empty()>. Однако при чтении из<circular_buffer>есть возможность полагаться на метод<at()>, который бросает исключение, когда индекс находится вне диапазона.
Итератор обычно считается недействительным, если элемент, на который указывает итератор, был удален или перезаписан другим элементом. Это определение обеспечивается поддержкой отладки и документируется для каждого метода. Однако некоторые приложения, использующие<circular_buffer>, могут требовать менее строгого определения: итератор недействителен только в том случае, если он указывает на неинициализированную память.
Рассмотрим следующий пример:
#define BOOST_CB_ENABLE_DEBUG 0 
#include <boost/circular_buffer.hpp>
#include <boost/assert.hpp>
#include <assert.h>
int main(int , char* [])
{
  boost::circular_buffer<int> cb(3);
  cb.push_back(1);
  cb.push_back(2);
  cb.push_back(3);
  boost::circular_buffer<int>::iterator it = cb.begin();
  assert(*it == 1);
  cb.push_back(4);
  assert(*it == 4); 
  return 0;
}
Итератор больше не указывает на исходный элемент (и считается недействительным с «строгой» точки зрения), но он по-прежнему указывает на то же действительное место в памяти. Это «мягкое» определение недействительности итератора поддерживается<circular_buffer>, но должно рассматриваться как деталь реализации, а не как полноценная функция. Правила, когда итератор все еще действителен, могут быть выведены из кода вsoft_iterator_invalidation.cpp.
Начиная с Boost 1.54.0 поддержка семантики движения была реализована с помощьюBoost. Перейдитев библиотеку. Если ссылки на значение r доступны<circular_buffer>, они будут использоваться, но если нет, то используется близкая, но несовершенная эмуляция. На таких компиляторах:
- Некопируемые объекты могут храниться в контейнерах. Они могут быть построены на месте с использованием<emplace>или если они поддерживают Boost. Двигайся, двигайся.
- Сами контейнеры не являются подвижными.
- Пересылка аргументов не идеальна.
<circular_buffer>будет использовать значения r и эмуляции перемещения для типов значений только в том случае, если конструктор перемещения и оператор назначения перемещения типа значения не бросают; или если тип значения не имеет конструктора копии.
Некоторые методы не будут использовать конструктор движения для типа значения вообще, если конструктор бросает. Это необходимо для согласованности данных и избежания ситуаций, когда в афтерере исключение<circular_buffer>содержит отошедшие объекты вместе с хорошими.
См. документацию для<is_copy_constructible>,<is_nothrow_move_assignable>и<is_nothrow_move_constructible>типов триат. Там вы найдете информацию о том, как сделать конструктор класса, и как сделать некопируемый класс в C++03 и C++98.
Производительность<circular_buffer>значительно улучшится, если тип значения не имеет ничего, кроме конструктора хода и назначения хода.
Справочная документация<circular_buffer>содержит примечания типа «Броски: см. Исключения<move_if_noexcept(T&)>». Это примечание означает следующее:<move_if_noexcept(T&
     value)>вообще не делает исключений, но оно возвращает<value>в качестве ссылки на значение r, только если класс<T>имеет конструктор ходов и оператор назначения ходов; или если у него нет конструктора копий. В противном случае<move_if_noexcept(T&
     value)>возвращается<value>в качестве ссылки.
Это приводит нас к следующей ситуации:
- Если<value>имеет не иначе как конструктор хода и не иначе как оператор назначения хода, то никакие исключения не будут брошены вообще.
- Если<value>имеет конструктор метательных движений и некоторый конструктор копий, то метод может выбрасывать исключения из конструктора копий.
- Если<value>не имеет конструктора копий, то метод может выбросить исключения из конструктора движений.
<move_if_noexcept(T&)>используетBoost.Move,<is_copy_constructible>,<is_nothrow_move_assignable>и<is_nothrow_move_constructible>тип триат.
<circular_buffer>не следует использовать для хранения указателей на динамически выделяемые объекты. Когда кольцевой буфер становится полным, дальнейшая вставка перезаписывает сохраненные указатели, что приводит к утечке памяти. Одним из рекомендуемых альтернатив является использование интеллектуальных указателей, напримерУсильте интеллектуальные указатели.
std::auto_ptr
| ![[Caution]](/img/caution.png) | Caution | 
| Особенно опасным считается любой контейнер<std::auto_ptr>. | 
| ![[Tip]](/img/tip.png) | Tip | 
| Никогда не создавайте круговой буфер<std::auto_ptr>. См. превосходную книгу Скотта Мейерса «Эффективный STL» для подробного обсуждения. (Мейерс С., Эффективный STL: 50 конкретных способов улучшить использование стандартной библиотеки шаблонов). Эддисон-Уэсли, 2001. | 
В то время как внутренние части<circular_buffer>являются круговыми,итераторы не являются. Итераторы<circular_buffer>действительны только для диапазона<\[begin(),end()\]>, поэтому, например: итераторы<(begin()-1)>и<(end()+
     1)>недействительны.
Чтобы помочь программисту избежать и найти распространенные ошибки, можно включить<circular_buffer>, чтобы обеспечить своего рода поддержку отладки.
Когда функция отладки включена,<circular_buffer>поддерживает список действительных итераторов. Как только какой-либо элемент будет уничтожен, все итераторы, указывающие на этот элемент, будут удалены из этого списка и явно признаны недействительными. Поддержка отладки также состоит из множества утверждений<BOOST_ASSERT>макросов, которые обеспечивают правильное использование<circular_buffer>и его итераторов во время выполнения. В случае использования недействительного итератора утверждение сообщит об ошибке. Связь явной итерации и утверждений делает очень надежный метод отладки, который улавливает большинство ошибок.
Кроме того, неинициализированная память, выделенная<circular_buffer>, заполнена значением<0xcc>в режиме отладки. При отладке кода это может помочь программисту распознать инициализированную память из неинициализированной. Подробно см. исходный кодcircular_buffer/debug.hpp.
| ![[Caution]](/img/caution.png) | Caution | 
| Поскольку код отладки делает<circular_buffer>и его итераторы более взаимосвязанными, гарантии безопасности потоков<circular_buffer>отличаются при включенной поддержке отладки. В дополнение к самому контейнеру все итераторы, отслеживаемые контейнером (включая любые его копии), должны быть защищены от одновременного доступа. В частности, это включает в себя копирование, уничтожение или получение итераторов из контейнера, даже если для доступа только для чтения. | 
Поддержка отладки отключена по умолчанию. Чтобы включить его, необходимо определить<BOOST_CB_ENABLE_DEBUG>макрос со значением 1 при компиляции кода с использованием<circular_buffer>.
<circular_buffer>совместим с библиотекойBoost.Interprocess, используемой для межпроцессной связи. Учитывая, что поддержка отладки circular_buffer основана на «сырых» указателях (что не допускается библиотекой Interprocess), код должен быть составлен с отключенной поддержкой отладки (т.е. с макросом<BOOST_CB_ENABLE_DEBUG>, не определенным или не определенным до 0). Если этого не сделать, компиляция потерпит неудачу.