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

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

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

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


person AraK    schedule 30.10.2009    source источник
comment
СМОТРИ ТАКЖЕ: 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 особенно хорош, когда вы понимаете, как его использовать.   -  person KitsuneYMG    schedule 30.10.2009


Ответы (8)


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

#include "SomeLibrary.h"

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

class UsingSomeLibrary {
   // ...
};

Предполагая, что SomeLibrary::Version объявлен как static const, а не как #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: error: static assertion failed: "Foo::bar is too small :("
person Mark Rushakoff    schedule 30.10.2009
comment
Я немного запутался, можете ли вы поместить static_assert в контекст неисполнения? Кажется, очень хороший пример :) - person AraK; 30.10.2009
comment
Да, статические утверждения в их нынешнем виде обычно реализуются как создание объекта, который определяется только в том случае, если предикат истинен. Это просто сделало бы глобальный. - person GManNickG; 30.10.2009
comment
я не уверен, что это квалифицируется как полный ответ на исходный вопрос, но хорошая демонстрация - person Matt Joiner; 30.10.2009
comment
Этот ответ не содержит подробностей о том, в чем разница между assert от ‹cassert› и static_assert. - person Alex Bitek; 16.01.2013
comment
@monocoder: см. абзац, начинающийся с Contrast with.... Вкратце: assert проверяет свое состояние во время выполнения, а static_assert проверяет свое состояние при компиляции. Поэтому, если условие, которое вы утверждаете, известно во время компиляции, используйте static_assert. Если условие не будет известно, пока программа не запустится, используйте assert. - person Mike DeSimone; 03.05.2013
comment
Не голосую, потому что я просто ненавижу примеры Foo и Bar, но в остальном хороший пример. - person Marc.2377; 14.09.2019

Статическое утверждение используется для создания утверждений во время компиляции. Когда статическое утверждение терпит неудачу, программа просто не компилируется. Это полезно в разных ситуациях, например, если вы реализуете какую-то функциональность кодом, который критически зависит от объекта 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.

person AnT    schedule 30.10.2009
comment
Разве static_assert НЕ ТРЕБУЕТСЯ иметь строковый литерал в качестве второго параметра? - person Trevor Hickey; 19.12.2013
comment
@ Тревор Хики: Да, это так. Но я не пытался конкретно ссылаться на static_assert из C++11. Мой static_assert выше - это просто абстрактная реализация статического утверждения. (Я лично использую что-то подобное в коде C). Мой ответ предназначен для общего назначения статических утверждений и их отличия от утверждений во время выполнения. - person AnT; 20.12.2013
comment
В первом примере вы предполагаете, что в переменной типа unsigned int нет битов заполнения. Это не гарантируется стандартом. Переменная типа unsigned int может законно занимать 32 бита памяти, оставляя 16 из них неиспользованными (и, таким образом, макрос UINT_MAX будет равен 65535). Таким образом, то, как вы описываете первое статическое утверждение (unsigned int объект, имеющий ровно 32 бита), вводит в заблуждение. Чтобы соответствовать вашему описанию, это утверждение также должно быть включено: static_assert(UINT_MAX >= 0xFFFFFFFFu). - person RalphS; 20.01.2019
comment
@TrevorHickey больше нет (C++17) - person luizfls; 20.07.2020

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

struct LogicalBlockAddress
{
#pragma pack(push, 1)
    Uint32 logicalBlockNumber;
    Uint16 partitionReferenceNumber;
#pragma pack(pop)
};
BOOST_STATIC_ASSERT(sizeof(LogicalBlockAddress) == 6);

В классе, обертывающем fseek() stdio.h, я использовал несколько ярлыков с 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 стандартизированы и могут обеспечить лучшую диагностику компилятора.

person Matt Joiner    schedule 30.10.2009

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

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

person nurettin    schedule 12.01.2010
comment
Теперь, когда C++11 вышел (и уже давно отсутствует), static_assert должен поддерживаться более поздними версиями всех основных компиляторов. Для тех из нас, кто не может дождаться C++14 (который, надеюсь, будет содержать ограничения шаблонов), это очень полезное применение static_assert. - person Talia; 15.06.2013

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

person Greg Hewgill    schedule 30.10.2009

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

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

// ...
}
person vladon    schedule 14.09.2015

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

Глава 2 (раздел 2.1) книги Андрея Александерску Modern_C++_Design" rel="nofollow">Modern_C++_Design реализует эту идею Compile. -time утверждения, подобные этому

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");
person nightlytrails    schedule 02.06.2014

static_assert можно использовать для запрета использования ключевого слова delete следующим образом:

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

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

person user11962338    schedule 23.08.2019