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(...)?


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 :(”

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.


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.


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.


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.


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

// ...
}

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

-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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.