1 апр. 2009 г.

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

Заголовочный файл <boost/static_assert.hpp> содержит определение макроса BOOST_STATIC_ASSERT(x), который генерирует сообщение об ошибке компиляции, если целочисленное константное выражение x не равно true. Другими словами, это эквивалент макросу assert на стадии компиляции; иногда эти средства называют "проверки во время компиляции", но здесь будут называться "статические проверки (static assertion)". Заметьте, что если условие равно true, то макрос не генерирует ни кода, ни данных, а также то, что этот макрос может быть использован в области видимости имен (scope) пространства имен, класса или функции. будучи использован в шаблоне, статическая проверка (static assertion) подставляется во время конкретизации шаблона (instantiation); это полезно для проверки параметров шаблона (см. - свойства типов).

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

Illegal use of COMPILE_TIME_ASSERTION_FAILURE<false>

Это, по меньшей мере, должно привлечь внимание!

Вы можете использовать макрос BOOST_STATIC_ASSERT в любом месте кода, где синтаксически допустимо поместить объявление, то есть в области видимости класса, функции или пространства имен. Эти варианту иллюстрируются следующими примерами:

Использование в области видимости пространства имен.

Макрос может быть использован в области видимости (scope) пространства имен (namespace), когда некое требование должно быть удовлетворено; обычно это означает некоторые специфичные для конкретной платформы требования. Допустим, нам необходимо удостовериться, что тип int имеет по крайней мере 32 бита, и что wchar_t является беззнаковым типом. Мы можем проверить это во время компиляции таки образом:

#include <climits>
#include <cwchar>
#include <boost/static_assert.hpp>

namespace my_conditions {

BOOST_STATIC_ASSERT(sizeof(int) * CHAR_BIT >= 32);
BOOST_STATIC_ASSERT(WCHAR_MIN >= 0);

} // namespace my_conditions

Такое использование пространства мен my_conditions требует некоторых пояснений. Макрос BOOST_STATIC_ASSERT работает через объявление типа оператором typedef, а так как typedef должен получить имя создаваемого синонима типа, то макрос создает это имя автоматически путем комбинирования имени-заглушки (stub name) с макросом __LINE__. Когда макрос BOOST_STATIC_ASSERT используется в области видимости класса или функции, то каждое использование BOOST_STATIC_ASSERT гарантированно дает уникальное для данной области видимости имя (при условии, что макрос используется единожды в каждой строке). Однако, при использовании макроса в хидере в области видимости пространства имен, надо учесть, что пространство имен может быть разбросано на множестве хидеров, каждый из которых может содержать собственные статические проверки (static assertions), причем на одинаковых строках, что приведет к дублированию объявлений. В теории компилятор должен молча игнорировать дубликаты для синонимов типов в typedef, однако не все следуют этому правилу (и даже если следуют, то иногда генерируют предупреждения в таких случаях). Чтобы избежать потенциальных проблем, в хитере в области видимости пространства имен следует использовать макрос BOOST_STATIC_ASSERT внутри уникального для данного хидера пространства имен.

Использование в области видимости функции

Макрос обычно используется в области видимости внутри шаблонной функции, когда необходимо проверять аргументы. Представьте, что у нас есть алгоритм, использующий итераторы, который требует итераторы с произвольным доступом (random access iterators). Если данный алгоритм будет конкретизирован (instantiated) с итератором, который не удовлетворяет такому условию, то ошибка рано или поздно возникнет, но она возможно появится в глубине нескольких подставляемых шаблонов, что делает затруднительным для пользователя определить причину ошибки. Один из методов решения - добавить статическую проверку на верхний уровень шаблона, так что если условие не соблюдается, то ошибка генерируется способом, которые делает очевидным для пользователя причину - неверное использование шаблона.

#include <iterator>
#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>

template <class RandomAccessIterator >
RandomAccessIterator foo(RandomAccessIterator from, RandomAccessIterator to)
{
   // this template can only be used with
   // random access iterators...
   typedef typename std::iterator_traits< RandomAccessIterator >::iterator_category cat;
   BOOST_STATIC_ASSERT((boost::is_convertible<cat, const std::random_access_iterator_tag&>::value));
   //
   // detail goes here...
   return from;
}

Вот парочка сносок по поводу приведенного примера. Во-первых, дополнительный набор круголых скобочек в макросе проверки необходим, чтобы не допустить интерпретации запятой внутри шаблона is_convertible как разделителя аргументов макроса. Во-вторых, целевой тип для шаблона is_convertible является ссылочным типом, так как некоторые компиляторы имеют проблемы при использовании шаблона is_convertible, когда преобразование выполняется через определенный пользователем конструктор; в любом случае не существует гарантий, что классы свойств итераторов (iterator tag classes) могут конструироваться копированием (copy-constructible).

Использование в области видимости класса

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

##include <climits>
#include <boost/static_assert.hpp>

template <class UnsignedInt>
class myclass
{
private:
   BOOST_STATIC_ASSERT(sizeof(UnsignedInt) * CHAR_BIT >= 16);
   BOOST_STATIC_ASSERT(std::numeric_limits<UnsignedInt>::is_specialized
                        && std::numeric_limits<UnsignedInt>::is_integer
                        && !std::numeric_limits<UnsignedInt>::is_signed);
public:
   /* details here */
};

Как это работает

BOOST_STATIC_ASSERT работает так. Есть класс STATIC_ASSERTION_FAILURE, определенный так:

namespace boost{

template <bool> struct STATIC_ASSERTION_FAILURE;

template <> struct STATIC_ASSERTION_FAILURE<true>{};

}

Ключевой деталью является то, что сообщение об ошибке включается посредством неопределенного выражения sizeof(STATIC_ASSERTION_FAILURE<0>), которое вроде бы правильно отрабатывает на большом множестве компиляторов. Оставшаяся машинерия макроса BOOST_STATIC_ASSERT это просто передача выражения sizeof в оператор typedef. В данном случае использование макроса является немного неуклюжим, однако разработчики библиотека BOOST потратили значительные усилия в попытках изобрести реализацию статических проверок без макросов, и все безуспешно. В итоге было решено, что выгода от правильной работы статической проверки в области видимости пространства имен, функции и класса перевешивает неуклюжесть макроса.

Тестовые программы

Следующие тестовые программы доступны в библиотеке:

Тестовая программа Компилируется ли Описание
static_assert_test.cpp Да Иллюстрирует использование. Должна всегда успешно компилироваться, в действительности проверяет совместимость компилятора.
static_assert_example_1.cpp Зависит от платформы. Тестовая программа для использования в области видимости пространства имен. Может успешно компилироваться на некоторых платформах.
static_assert_example_2.cpp Да Тестовая программа для области видимости функции.
static_assert_example_3.cpp Да Тестовая программа для области видимости класса.
static_assert_test_fail_1.cpp Нет Пример генерации ошибки в области видимости пространства имен.
static_assert_test_fail_2.cpp Нет Пример генерации ошибки в области видимости не-шаблонной функции.
static_assert_test_fail_3.cpp Нет Пример генерации ошибки в области видимости не-шаблонного класса.
static_assert_test_fail_4.cpp Нет Пример генерации ошибки в области видимости не-шаблонного класса.
static_assert_test_fail_5.cpp Нет Пример генерации ошибки в области видимости шаблонного класса.
static_assert_test_fail_6.cpp Нет Пример генерации ошибки в области видимости метода шаблонного класса.
static_assert_test_fail_7.cpp Нет Пример генерации ошибки в области видимости класса.
static_assert_test_fail_8.cpp Нет Пример генерации ошибки в области видимости функции.
static_assert_test_fail_9.cpp Нет Пример генерации ошибки в области видимости функции (часть 2).

P.S.: Нагло спёрто отсюда: BOOST C++: проверки на стадии компиляции (static assertions)

Комментариев нет:

Отправить комментарий