Co robi static_assert i do czego byś go użył?

117

Czy mógłbyś podać przykład, w którym static_assert(...)('C ++ 11') elegancko rozwiązałoby problem?

Znam się na czasie wykonywania assert(...). Kiedy powinienem preferować static_assert(...)zamiast zwykłego assert(...)?

Poza boosttym jest coś BOOST_STATIC_ASSERT, co się nazywa , czy to jest to samo co static_assert(...)?

AraK
źródło
ZOBACZ TEŻ: 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], aby uzyskać więcej opcji. _MSG jest szczególnie przyjemny, gdy się zorientujesz, jak go używać.
KitsuneYMG

Odpowiedzi:

82

Z czubka mojej głowy ...

#include "SomeLibrary.h"

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

class UsingSomeLibrary {
   // ...
};

Zakładając, że SomeLibrary::Versionjest zadeklarowana jako stała statyczna, a nie #defined (jak można by oczekiwać w bibliotece C ++).

Porównajmy to z koniecznością rzeczywistego skompilowania SomeLibraryi skompilowania kodu, połączenia wszystkiego i uruchomienia pliku wykonywalnego tylko wtedy, aby dowiedzieć się, że spędziłeś 30 minut na kompilowaniu niezgodnej wersji SomeLibrary.

@Arak, w odpowiedzi na twój komentarz: tak, możesz po static_assertprostu siedzieć gdziekolwiek, z wyglądu:

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: błąd: potwierdzenie statyczne nie powiodło się: „Foo :: bar jest za mały :(”
Mark Rushakoff
źródło
1
Jestem trochę zdezorientowany, czy możesz umieścić static_assertw kontekście braku wykonania? Wydaje się, że to bardzo fajny przykład :)
AraK
3
Tak, statyczne potwierdzenia są zwykle implementowane jako tworzenie obiektu, który jest zdefiniowany tylko wtedy, gdy predykat jest prawdziwy. To po prostu uczyniłoby to globalnym.
GManNickG
nie jestem pewien, czy to kwalifikuje się jako odpowiedź na oryginalne pytanie w całości, ale fajna demonstracja
Matt Joiner
2
Ta odpowiedź nie zawiera żadnych szczegółów na temat tego, co jest różnica między dochodzić od <cassert> i static_assert
Bitek
11
@monocoder: Zobacz akapit zaczynający się od „Kontrast z ...”. W skrócie: assert sprawdza swój stan w czasie wykonywania, a static_assert sprawdza jego stan podczas kompilacji. Jeśli więc warunek, który zapewniasz, jest znany w czasie kompilacji, użyj static_assert. Jeśli warunek nie będzie znany do czasu uruchomienia programu, użyj assert.
Mike DeSimone
131

Asercja statyczna służy do tworzenia asercji w czasie kompilacji. Gdy asercja statyczna nie powiedzie się, program po prostu się nie kompiluje. Jest to przydatne w różnych sytuacjach, na przykład jeśli zaimplementujesz jakąś funkcjonalność za pomocą kodu, który w znacznym stopniu zależy od unsigned intobiektu mającego dokładnie 32 bity. Możesz umieścić takie statyczne potwierdzenie

static_assert(sizeof(unsigned int) * CHAR_BIT == 32);

w swoim kodzie. Na innej platformie, z innym unsigned inttypem kompilacji, kompilacja zakończy się niepowodzeniem, co zwróci uwagę programisty na problematyczną część kodu i doradzi mu ponowne zaimplementowanie lub ponowne sprawdzenie.

Na przykład, możesz chcieć przekazać jakąś wartość całkowitą jako void *wskaźnik do funkcji (hack, ale czasami przydatny) i chcesz się upewnić, że wartość całkowita będzie pasować do wskaźnika

int i;

static_assert(sizeof(void *) >= sizeof i);
foo((void *) i);

Możesz chcieć, aby zasób tego chartypu był podpisany

static_assert(CHAR_MIN < 0);

lub ten całkowy podział z wartościami ujemnymi zaokrągla się do zera

static_assert(-5 / 2 == -2);

I tak dalej.

W wielu przypadkach potwierdzenia w czasie wykonywania mogą być używane zamiast potwierdzeń statycznych, ale asercje w czasie wykonywania działają tylko w czasie wykonywania i tylko wtedy, gdy kontrola przechodzi przez potwierdzenie. Z tego powodu niepomyślna asercja w czasie wykonywania może pozostawać uśpiona i niewykryta przez dłuższy czas.

Oczywiście wyrażenie w asercji statycznej musi być stałą czasu kompilacji. Nie może to być wartość czasu wykonywania. W przypadku wartości czasu wykonywania nie masz innego wyjścia, jak tylko użyć zwykłego assert.

Mrówka
źródło
3
Czy static_assert nie jest WYMAGANE, aby jako drugi parametr mieć literał ciągu?
Trevor Hickey,
3
@Trevor Hickey: Tak, jest. Ale nie próbowałem odwoływać się static_assertkonkretnie z C ++ 11. Moja static_assertpowyżej to tylko niektóre streszczenie realizacja twierdzenie statycznej. (Osobiście używam czegoś takiego w kodzie C). Moja odpowiedź ma dotyczyć ogólnego celu asercji statycznych i ich różnicy w stosunku do asercji w czasie wykonywania.
AnT
W pierwszym przykładzie zakładasz, że w zmiennej typu nie ma bitów wypełniających unsigned int. Nie jest to gwarantowane przez standard. Zmienna typu unsigned intmogłaby legalnie zajmować 32 bity pamięci, pozostawiając 16 z nich nieużywanych (a zatem makro UINT_MAXbyłoby równe 65535). Zatem sposób, w jaki opisujesz pierwsze stwierdzenie statyczne („ unsigned intobiekt mający dokładnie 32 bity”) jest mylący. Pasujące do Twojego opisu, to stwierdzenie powinno być również uwzględnione: static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS
@TrevorHickey już nie (C ++ 17)
luizfls
13

Używam go, aby upewnić się, że moje założenia dotyczące zachowania kompilatora, nagłówków, bibliotek, a nawet mojego własnego kodu są poprawne. Na przykład tutaj sprawdzam, czy struktura została poprawnie zapakowana do oczekiwanego rozmiaru.

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

W opakowaniu klasy stdio.h„s fseek(), mam podjąć jakieś skróty z enum Origini sprawdzić, czy te skróty wyrównać ze stałych zdefiniowanych przezstdio.h

uint64_t BasicFile::seek(int64_t offset, enum Origin origin)
{
    BOOST_STATIC_ASSERT(SEEK_SET == Origin::SET);

Należy wolisz static_assertponad assertgdy zachowanie jest określona w czasie kompilacji, a nie w czasie wykonywania, takie jak przykładów mam podanych powyżej. Przykład, w którym tak nie jest , obejmowałby sprawdzanie parametrów i kodu powrotu.

BOOST_STATIC_ASSERTjest makrem sprzed C ++ 0x, które generuje niedozwolony kod, jeśli warunek nie jest spełniony. Zamierzenia są takie same, aczkolwiek static_assertjest on ustandaryzowany i może zapewnić lepszą diagnostykę kompilatora.

Matt Joiner
źródło
9

BOOST_STATIC_ASSERT jest aplikacją typu wrapper dla platformy static_assert funkcjonalności.

Obecnie używam static_assert w celu wymuszenia „Pojęcia” na klasie.

przykład:

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

Spowoduje to błąd czasu kompilacji, jeśli którykolwiek z powyższych warunków nie zostanie spełniony.

nurettin
źródło
3
Teraz, gdy C ++ 11 jest niedostępny (i był niedostępny przez jakiś czas), static_assert powinno być obsługiwane przez nowsze wersje wszystkich głównych kompilatorów. Dla tych z nas, którzy nie mogą się doczekać C ++ 14 (który, miejmy nadzieję, będzie zawierał ograniczenia szablonowe), jest to bardzo przydatna aplikacja static_assert.
Collin,
7

Jednym z zastosowań static_assertmoże być upewnienie się, że struktura (czyli interfejs ze światem zewnętrznym, takim jak sieć lub plik) ma dokładnie taki rozmiar, jakiego oczekujesz. To wychwyciłoby przypadki, w których ktoś dodaje lub modyfikuje element ze struktury bez zdawania sobie sprawy z konsekwencji. static_assertBy go podnieść i ostrzega użytkownika.

Greg Hewgill
źródło
3

W przypadku braku koncepcji można użyć static_assertdo prostego i czytelnego sprawdzania typów w czasie kompilacji, na przykład w szablonach:

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

// ...
}
vladon
źródło
2

Nie stanowi to bezpośredniej odpowiedzi na pierwotne pytanie, ale stanowi interesujące badanie, w jaki sposób wymusić te kontrole czasu kompilacji przed C ++ 11.

Rozdział 2 (sekcja 2.1) Modern C ++ Design autorstwa Andrei Alexanderscu implementuje taką ideę asercji w czasie kompilacji, jak ta

template<int> struct CompileTimeError;
template<> struct CompileTimeError<true> {};

#define STATIC_CHECK(expr, msg) \
{ CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; } 

Porównaj makro STATIC_CHECK () i static_assert ()

STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");
nocne szlaki
źródło
-2

static_assertMogą być wykorzystane do zabrania używania deletesłów kluczowych w ten sposób:

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

Każdy współczesny programista C ++ może chcieć to zrobić, jeśli chce użyć konserwatywnego modułu odśmiecania pamięci, używając tylko klas es i struktur, które przeciążają operator new, aby wywołać funkcję, która przydziela pamięć na konserwatywnym stercie konserwatywnego modułu odśmiecania pamięci, który można zainicjować i utworzyć instancję, wywołując jakąś funkcję, która robi to na początku mainfunkcji.

Na przykład każdy współczesny programista C ++, który chce użyć konserwatywnego garbage collectora Boehm-Demersa-Weisera, na początku mainfunkcji napisze:

GC_init();

I w każdym classi structprzeciążaj w operator newten sposób:

void* operator new(size_t size)
{
     return GC_malloc(size);
}

A teraz, gdy operator deletenie jest już potrzebny, ponieważ konserwatywny moduł odśmiecania Boehm-Demers-Weiser jest odpowiedzialny zarówno za zwalnianie, jak i zwalnianie każdego bloku pamięci, gdy nie jest już potrzebny, programista chce zabronićdelete słowa kluczowego.

Jednym ze sposobów jest przeciążenie w delete operatorten sposób:

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

Nie jest to jednak zalecane, ponieważ współczesny programista C ++ będzie wiedział, że omyłkowo wywołał plik delete operator czas wykonywania, ale lepiej to wiedzieć wkrótce w czasie kompilacji.

Więc moim zdaniem najlepszym rozwiązaniem tego scenariusza jest użycie static_assert co pokazano na początku tej odpowiedzi.

Oczywiście można to również zrobić BOOST_STATIC_ASSERT, ale myślę, że static_assertjest to lepsze i zawsze powinno być preferowane.

user11962338
źródło