Що робить static_assert і для чого ви його використовуєте?


117

Чи можете ви навести приклад, коли static_assert(...)("C ++ 11") буде вирішувати проблему в руках елегантно?

Я знайомий з часом виконання 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це оголошено як статичний 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: помилка: не вдалося статичне твердження: "Foo :: bar is too small :("

1
Я трохи розгублений, чи можете ви поставити його static_assertв контекст невиконання? Це здається дуже приємним прикладом :)
AraK

3
Так, статичні твердження в їхньому стані зазвичай реалізуються як створення об'єкта, який визначається лише за умови, що предикат є істинним. Це просто зробить глобальним.
GManNickG

Я не впевнений, що це відповідає повною мірою як відповідь на оригінальне запитання, але приємна демонстрація
Метт Столяр

2
Ця відповідь не містить жодних деталей про те, у чому різниця між ствердженням від <cassert> і static_assert
bitek

11
@monocoder: Дивіться абзац, починаючи з "Контраст із ...". Коротше кажучи , стверджувати , перевірки його стану під час виконання, і 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 НЕ ПОТРІБНО мати рядковий літер як другий параметр?
Тревор Хікі

3
@Trevor Хікі: Так, так і є. Але я не намагався static_assertконкретно посилатися на C ++ 11. Моє static_assertвище - лише деяка абстрактна реалізація статичного твердження. (Я особисто використовую щось подібне в коді С). Моя відповідь спрямована на те, щоб загальна мета статичних тверджень та їх відмінність від тверджень під час виконання.
ANT

У першому прикладі ви припускаєте, що в змінній типу немає біт для прокладки unsigned int. Це не гарантується стандартом. Змінна типу unsigned intможе юридично займати 32 біти пам'яті, залишаючи 16 з них невикористаними (і, таким чином, макрос UINT_MAXбуде дорівнює 65535). Таким чином, спосіб опису першого статичного твердження (" unsigned intоб'єкт має рівно 32 біти") вводить в оману. Для того, щоб відповідати вашому опису, це твердження має бути включено , а також: static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS

@TrevorHickey більше не (C ++ 17)
luizfls

13

Я використовую це, щоб переконатися, що мої припущення щодо поведінки компілятора, заголовків, libs і навіть власного коду є правильними. Наприклад, тут я перевіряю, що структура була правильно упакована до очікуваного розміру.

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це макрос 0-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.
Колін

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 та структури , які перевантажують оператора новим, щоб викликати функцію, що виділяє пам'ять на консервативну купу консервативного сміттєзбірника, який можна ініціалізувати та інстанціювати, викликаючи деяку функцію, яка робить це на початку 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це краще і слід віддавати перевагу завжди.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.