Что делает static_assert и для чего вы его используете?

117

Не могли бы вы привести пример, в котором static_assert(...)('C ++ 11') элегантно решит поставленную задачу?

Я знаком с run-time assert(...). Когда я должен предпочесть static_assert(...)обычному assert(...)?

Кроме того, boostтам что-то называется BOOST_STATIC_ASSERT, это то же самое, что и static_assert(...)?

Арак
источник
СМОТРИ ТАКЖЕ: BOOST_MPL_ASSERT, BOOST_MPL_ASSERT_NOT, BOOST_MPL_ASSERT_MSG, BOOST_MPL_ASSERT_RELATION [ boost.org/doc/libs/1_40_0/libs/mpl/doc/refmanual/asserts.html] для получения дополнительных параметров. _MSG особенно хорош, когда вы понимаете, как его использовать.
KitsuneYMG

Ответы:

82

С верхней части моей головы...

#include "SomeLibrary.h"

static_assert(SomeLibrary::Version > 2, 
         "Old versions of SomeLibrary are missing the foo functionality.  Cannot proceed!");

class UsingSomeLibrary {
   // ...
};

Предполагая, что SomeLibrary::Versionон объявлен как статическая константа, а не как #defined (как можно было бы ожидать в библиотеке C ++).

Сравните это с необходимостью компилировать 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: ошибка: статическое утверждение не удалось: "Foo :: bar слишком мал :("
Марк Рушаков
источник
1
Я немного запутался, можете вы поставить static_assertконтекст невыполнения? Кажется, это очень хороший пример :)
AraK
3
Да, статические утверждения в существующем виде обычно реализуются как создание объекта, который определяется только в том случае, если предикат истинен. Это просто сделало бы global.
GManNickG
Я не уверен, что это можно считать полным ответом на исходный вопрос, но хорошая демонстрация
Мэтт Джойнер
2
В этом ответе нет подробностей о том, в чем разница между assert от <cassert> и static_assert
bitek
11
@monocoder: См. абзац, начинающийся с «Контраст с ...». Вкратце: assert проверяет свое состояние во время выполнения, а static_assert проверяет его состояние при компиляции. Итак, если условие, которое вы утверждаете, известно во время компиляции, используйте static_assert. Если условие не будет известно до запуска программы, используйте assert.
Майк Дезимоун
131

Статическое утверждение используется для выполнения утверждений во время компиляции. Когда статическое утверждение терпит неудачу, программа просто не компилируется. Это полезно в различных ситуациях, например, если вы реализуете некоторые функции с помощью кода, который критически зависит от 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
Разве static_assert НЕ ТРЕБУЕТСЯ иметь строковый литерал в качестве второго параметра?
Trevor Hickey
3
@ Тревор Хикки: Да, это так. Но я не пытался static_assertконкретно ссылаться на C ++ 11. Мое static_assertвыше - это просто абстрактная реализация статического утверждения. (Я лично использую что-то подобное в коде C). Мой ответ предназначен для общего назначения статических утверждений и их отличий от утверждений времени выполнения.
AnT
В первом примере предполагается, что в переменной типа нет битов заполнения unsigned int. Стандарт не гарантирует этого. Переменная типа unsigned intможет законно занимать 32 бита памяти, оставляя 16 из них неиспользованными (и, следовательно, макрос UINT_MAXбудет равен 65535). Таким образом, то, как вы описываете первое статическое утверждение (« unsigned intобъект, имеющий ровно 32 бита»), вводит в заблуждение. Для того, чтобы соответствовать вашему описанию, это утверждение должно быть включено , а также: static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS
@TrevorHickey больше нет (C ++ 17)
luizfls
13

Я использую его, чтобы убедиться, что мои предположения о поведении компилятора, заголовках, библиотеках и даже моем собственном коде верны. Например, здесь я проверяю, что структура была правильно упакована до ожидаемого размера.

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- это макрос до C ++ 0x, который генерирует недопустимый код, если условие не выполняется. Намерения те же самые, хотя static_assertстандартизированы и могут обеспечить лучшую диагностику компилятора.

Мэтт Джойнер
источник
9

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 ... */
};

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

Нуреттин
источник
3
Теперь, когда C ++ 11 отсутствует (и не используется уже некоторое время), static_assert должен поддерживаться более поздними версиями всех основных компиляторов. Для тех из нас, кто не может дождаться C ++ 14 (который, надеюсь, будет содержать ограничения шаблона), это очень полезное приложение static_assert.
Collin
7

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

Грег Хьюгилл
источник
3

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

template <class T>
void MyFunc(T value)
{
static_assert(std::is_base_of<MyBase, T>::value, 
              "T must be derived from MyBase");

// ...
}
vladon
источник
2

Это не дает прямого ответа на исходный вопрос, но делает интересное исследование того, как обеспечить выполнение этих проверок времени компиляции до C ++ 11.

Глава 2 (раздел 2.1) « Современного дизайна на C ++» Андрея Александерску реализует эту идею утверждений времени компиляции, подобных этой.

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");
nightlytrails
источник
-2

static_assertМожет быть использован , чтобы запретить использование deleteключевого слова таким образом:

#define delete static_assert(0, "The keyword \"delete\" is forbidden.");

Каждый современный разработчик C ++ может захотеть сделать это, если он или она хочет использовать консервативный сборщик мусора, используя только классы es и struct s, которые перегружают оператор 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больше не требуется, поскольку консервативный сборщик мусора Бем-Демерс-Вайзер отвечает как за освобождение, так и за освобождение каждого блока памяти, когда он больше не нужен, разработчик хочет запретить deleteключевое слово.

Один из способов - перегрузить delete operator следующим образом:

void operator delete(void* ptr)
{
    assert(0);
}

Но это не рекомендуется, потому что современный разработчик C ++ будет знать, что он / она по ошибке вызвал время delete operatorвыполнения, но лучше узнать это как можно скорее во время компиляции.

Поэтому, на мой взгляд, лучшим решением этого сценария является использование того, static_assertчто показано в начале этого ответа.

Конечно, это тоже можно сделать с помощью BOOST_STATIC_ASSERT, но я думаю, что static_assertэто лучше и всегда следует отдавать предпочтение.

user11962338
источник