Что делает static_assert и для чего вы его используете?
Не могли бы вы привести пример, где static_assert(...)
('С++ 11') изящно решит проблему?
Я знаком со временем выполнения assert(...)
. Когда мне следует отдавать предпочтение static_assert(...)
, а не обычному assert(...)
?
Кроме того, в boost
есть что-то под названием BOOST_STATIC_ASSERT
, это то же самое, что и static_assert(...)
?
Ответы
Ответ 1
Сверху моей головы...
#include "SomeLibrary.h"
static_assert(SomeLibrary::Version > 2,
"Old versions of SomeLibrary are missing the foo functionality. Cannot proceed!");
class UsingSomeLibrary {
// ...
};
Предполагая, что SomeLibrary::Version
объявляется как статический const, а не #define
d (как и следовало ожидать в библиотеке С++).
Контраст с необходимостью компилировать SomeLibrary
и ваш код, связать все и запустить исполняемый файл только тогда, чтобы узнать, что вы потратили 30 минут на компиляцию несовместимой версии SomeLibrary
.
@Arak, в ответ на ваш комментарий: да, вы можете static_assert
просто сидеть где угодно, от внешнего вида:
class Foo
{
public:
static const int bar = 3;
};
static_assert(Foo::bar > 4, "Foo::bar is too small :(");
int main()
{
return Foo::bar;
}
$ g++ --std=c++0x a.cpp
a.cpp:7: error: static assertion failed: "Foo::bar is too small :("
Ответ 2
Статическое утверждение используется для выполнения утверждений во время компиляции. Когда статическое утверждение терпит неудачу, программа просто не компилируется. Это полезно в разных ситуациях, например, если вы реализуете некоторые функции с помощью кода, который критически зависит от объекта unsigned int
, имеющего ровно 32 бита. Вы можете поставить статическое утверждение вроде этого
static_assert(sizeof(unsigned int) * CHAR_BIT == 32);
в вашем коде. На другой платформе с типом unsigned int
с разным размером компиляция завершится неудачей, обратив внимание разработчика на проблемную часть кода и посоветовав им повторно реализовать или повторно проверить его.
В другом примере вам может потребоваться передать некоторое целое значение в качестве указателя void *
к функции (взломать, но полезно время от времени), и вы хотите убедиться, что интегральное значение будет вписываться в указатель
int i;
static_assert(sizeof(void *) >= sizeof i);
foo((void *) i);
Вам может потребоваться активация типа char
static_assert(CHAR_MIN < 0);
или интегральное деление с отрицательными значениями округляется к нулю
static_assert(-5 / 2 == -2);
И так далее.
Во многих случаях могут использоваться утверждения времени выполнения вместо статических утверждений, но утверждения во время выполнения работают только во время выполнения и только тогда, когда управление передает это утверждение. По этой причине неудачное утверждение во время выполнения может оставаться бездействующим, необнаруженным в течение длительных периодов времени.
Конечно, выражение в статическом утверждении должно быть константой времени компиляции. Это не может быть значением времени выполнения. Для значений времени выполнения у вас нет другого выбора, кроме обычного assert
.
Ответ 3
Я использую его, чтобы убедиться, что мои предположения о поведении компилятора, заголовках, библиотеках и даже моем собственном коде верны. Например, здесь я проверяю, что структура была правильно упакована до ожидаемого размера.
struct LogicalBlockAddress
{
#pragma pack(push, 1)
Uint32 logicalBlockNumber;
Uint16 partitionReferenceNumber;
#pragma pack(pop)
};
BOOST_STATIC_ASSERT(sizeof(LogicalBlockAddress) == 6);
В обертке класса stdio.h
fseek()
я взял несколько ярлыков с enum Origin
и убедитесь, что эти ярлыки совпадают с константами, определяемыми stdio.h
uint64_t BasicFile::seek(int64_t offset, enum Origin origin)
{
BOOST_STATIC_ASSERT(SEEK_SET == Origin::SET);
Вы должны предпочесть static_assert
над assert
, когда поведение определено во время компиляции, а не во время выполнения, например, приведенные выше примеры. Пример, когда это не так, будет включать проверку кода и кода возврата.
BOOST_STATIC_ASSERT
- это макрос pre-С++ 0x, который генерирует незаконный код, если условие не выполняется. Намерения одинаковы, хотя static_assert
стандартизирован и может обеспечить лучшую диагностику компилятора.
Ответ 4
BOOST_STATIC_ASSERT
- это кросс-платформенная оболочка для static_assert
.
В настоящее время я использую static_assert для принудительного применения "Концепций" в классе.
Пример:
template <typename T, typename U>
struct Type
{
BOOST_STATIC_ASSERT(boost::is_base_of<T, Interface>::value);
BOOST_STATIC_ASSERT(std::numeric_limits<U>::is_integer);
/* ... more code ... */
};
Это приведет к ошибке времени компиляции, если какое-либо из вышеперечисленных условий не будет выполнено.
Ответ 5
Одно использование static_assert
может заключаться в том, чтобы структура (то есть интерфейс с внешним миром, такой как сеть или файл) была точно такой, какой вы ожидаете. Это будет ловить случаи, когда кто-то добавляет или изменяет члена из структуры, не осознавая последствий. static_assert
подберет его и предупредит пользователя.
Ответ 6
В отсутствие понятий можно использовать static_assert
для простой и читаемой проверки типа компиляции, например, в шаблонах:
template <class T>
void MyFunc(T value)
{
static_assert(std::is_base_of<MyBase, T>::value,
"T must be derived from MyBase");
// ...
}
Ответ 7
Это напрямую не отвечает на исходный вопрос, но делает интересное исследование того, как применять эти проверки времени компиляции до С++ 11.
Глава 2 (Раздел 2.1) "Современный дизайн С++" от Andrei Alexanderscu реализует эту идею утверждений времени компиляции, таких как
template<int> struct CompileTimeError;
template<> struct CompileTimeError<true> {};
#define STATIC_CHECK(expr, msg) \
{ CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; }
Сравните макрос STATIC_CHECK() и static_assert()
STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");
Ответ 8
static_assert
можно использовать, чтобы запретить использование ключевого слова delete
следующим образом:
#define delete static_assert(0, "The keyword \"delete\" is forbidden.");
Каждый современный разработчик C++ может захотеть сделать это, если он или она хочет использовать консервативный сборщик мусора, используя только класс es и struct, которые перегружают оператор new чтобы вызвать функцию, которая выделяет память в консервативной куче консервативного сборщика мусора, который можно инициализировать и создать, вызвав некоторую функцию, которая делает это в начале функции main
.
Например, каждый современный разработчик C++, который хочет использовать консервативный сборщик мусора Boehm-Demers-Weiser, в начале функции main
напишет:
GC_init();
И в каждой class
и struct
перегрузите operator new
следующим образом:
void* operator new(size_t size)
{
return GC_malloc(size);
}
И теперь, когда operator delete
больше не нужен, так как консервативный сборщик мусора Boehm-Demers-Weiser отвечает как за освобождение, так и за освобождение каждого блока памяти, когда он больше не нужен, разработчик хочет запретить delete
ключевое слово.
Один из способов - перегрузить delete operator
таким образом:
void operator delete(void* ptr)
{
assert(0);
}
Но это не рекомендуется, потому что современный разработчик C++ будет знать, что он/она по ошибке вызвал delete operator
во время выполнения, но лучше знать это вскоре во время компиляции.
Поэтому, на мой взгляд, лучшее решение этого сценария - использовать static_assert
, как показано в начале этого ответа.
Конечно, это также может быть сделано с BOOST_STATIC_ASSERT
, но я думаю, что static_assert
лучше и его следует предпочитать всегда.