Что делает 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");

// ...
}

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");

-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это лучше и всегда следует отдавать предпочтение.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.