Cosa fa static_assert e per cosa lo useresti?


117

Potresti fare un esempio in cui static_assert(...)('C ++ 11') risolverebbe elegantemente il problema?

Conosco il tempo di esecuzione assert(...). Quando dovrei preferire static_assert(...)il normale assert(...)?

Inoltre, boostc'è qualcosa chiamato BOOST_STATIC_ASSERT, è lo stesso di static_assert(...)?


VEDERE ANCHE: 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] per ulteriori opzioni. _MSG è particolarmente utile una volta che hai capito come usarlo.
KitsuneYMG

Risposte:


82

Fuori dalla mia testa ...

#include "SomeLibrary.h"

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

class UsingSomeLibrary {
   // ...
};

Supponendo che SomeLibrary::Versionsia dichiarato come const statico, piuttosto che essere #defined (come ci si aspetterebbe in una libreria C ++).

Contrasta con la necessità di compilare effettivamente SomeLibrarye il tuo codice, collegare tutto ed eseguire l'eseguibile solo allora per scoprire che hai impiegato 30 minuti a compilare una versione incompatibile di SomeLibrary.

@Arak, in risposta al tuo commento: sì, puoi static_assertstare seduto ovunque, a quanto pare:

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: errore: asserzione statica non riuscita: "Foo :: bar è troppo piccolo :("

1
Sono un po 'confuso, puoi inserire static_assertun contesto di non esecuzione? Sembra un esempio molto carino :)
AraK

3
Sì, le asserzioni statiche così come sono di solito vengono implementate per creare un oggetto definito solo se il predicato è vero. Questo farebbe solo un globale.
GManNickG

Non sono sicuro che questo si qualifichi come rispondere alla domanda originale nella sua interezza, ma bella dimostrazione
Matt Joiner

2
Questa risposta non fornisce alcun dettaglio su quale sia la differenza tra assert from <cassert> e static_assert
bitek

11
@monocoder: vedere il paragrafo che inizia con "Contrasto con ...". In breve: assert controlla la sua condizione in fase di esecuzione e static_assert controlla la sua condizione durante la compilazione. Quindi, se la condizione che stai affermando è nota in fase di compilazione, usa static_assert. Se la condizione non sarà nota finché il programma non viene eseguito, utilizzare assert.
Mike DeSimone

131

L'asserzione statica viene utilizzata per fare asserzioni in fase di compilazione. Quando l'asserzione statica fallisce, il programma semplicemente non si compila. Ciò è utile in diverse situazioni, come, ad esempio, se si implementano alcune funzionalità tramite codice che dipendono in modo critico unsigned intdall'oggetto che ha esattamente 32 bit. Puoi mettere un'affermazione statica come questa

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

nel codice. Su un'altra piattaforma, con un unsigned inttipo di dimensioni diverse la compilazione fallirà, attirando così l'attenzione dello sviluppatore sulla parte problematica del codice e consigliandogli di reimplementarla o riesaminarla.

Per un altro esempio, potresti voler passare un valore integrale come void *puntatore a una funzione (un trucco, ma a volte utile) e vuoi assicurarti che il valore integrale si adatti al puntatore

int i;

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

Potresti voler fare in modo che quel chartipo sia firmato

static_assert(CHAR_MIN < 0);

o quella divisione integrale con valori negativi arrotonda verso zero

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

E così via.

Le asserzioni in fase di esecuzione in molti casi possono essere utilizzate al posto delle asserzioni statiche, ma le asserzioni in fase di esecuzione funzionano solo in fase di esecuzione e solo quando il controllo passa sull'asserzione. Per questo motivo un'asserzione in fase di esecuzione non riuscita può rimanere inattiva, non rilevata per lunghi periodi di tempo.

Ovviamente, l'espressione nell'asserzione statica deve essere una costante del tempo di compilazione. Non può essere un valore di runtime. Per i valori di runtime non hai altra scelta che usare l'ordinario assert.


3
Static_assert non è RICHIESTO per avere una stringa letterale come secondo parametro?
Trevor Hickey

3
@Trevor Hickey: Sì, lo è. Ma non stavo cercando di fare riferimento specificamente a static_assertC ++ 11. Quella static_assertsopra è solo un'implementazione astratta dell'asserzione statica. (Io personalmente uso qualcosa del genere nel codice C). La mia risposta intende riguardare lo scopo generale delle asserzioni statiche e la loro differenza dalle asserzioni in fase di esecuzione.
AnT

Nel primo esempio, stai assumendo che non ci siano bit di riempimento in una variabile di tipo unsigned int. Questo non è garantito dallo standard. Una variabile di tipo unsigned intpotrebbe occupare legalmente 32 bit di memoria, lasciandone 16 inutilizzati (e quindi la macro UINT_MAXsarebbe uguale a 65535). Quindi il modo in cui descrivi la prima asserzione statica (" unsigned intoggetto con esattamente 32 bit") è fuorviante. Per abbinare descrizione, questa affermazione dovrebbe essere incluso pure: static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS

@TrevorHickey non più (C ++ 17)
luizfls

13

Lo uso per assicurarmi che le mie ipotesi sul comportamento del compilatore, le intestazioni, le librerie e persino il mio codice siano corrette. Ad esempio qui verifico che la struttura sia stata correttamente imballata alla dimensione prevista.

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

In una classe involucro stdio.h's fseek(), ho preso alcune scorciatoie con enum Origine verifica che le scorciatoie siano allineati con le costanti definite dastdio.h

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

Si dovrebbe preferire static_assertsopra assertquando il comportamento è definito al momento della compilazione, e non in fase di esecuzione, come ad esempio gli esempi che ho dato sopra. Un esempio in cui questo non è il caso includerebbe il controllo dei parametri e del codice di ritorno.

BOOST_STATIC_ASSERTè una macro pre-C ++ 0x che genera codice illegale se la condizione non è soddisfatta. Le intenzioni sono le stesse, anche se static_assertè standardizzato e può fornire una migliore diagnostica del compilatore.


9

BOOST_STATIC_ASSERTè un wrapper multipiattaforma per static_assertfunzionalità.

Attualmente sto usando static_assert per applicare "Concepts" a una classe.

esempio:

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

Ciò causerà un errore in fase di compilazione se una delle condizioni precedenti non viene soddisfatta.


3
Ora che C ++ 11 è uscito (ed è uscito per un po '), static_assert dovrebbe essere supportato dalle versioni più recenti di tutti i principali compilatori. Per quelli di noi che non possono aspettare C ++ 14 (che si spera conterrà i vincoli del modello), questa è un'applicazione molto utile di static_assert.
Collin

7

Un utilizzo static_assertpotrebbe essere quello di garantire che una struttura (che è un'interfaccia con il mondo esterno, come una rete o un file) sia esattamente della dimensione che ti aspetti. Ciò catturerebbe i casi in cui qualcuno aggiunge o modifica un membro dalla struttura senza rendersi conto delle conseguenze. Lo static_assertraccoglieranno e avviseranno l'utente.


3

In assenza di concetti è possibile utilizzare static_assertper il controllo del tipo in fase di compilazione semplice e leggibile, ad esempio, nei modelli:

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

// ...
}

2

Questo non risponde direttamente alla domanda originale, ma costituisce uno studio interessante su come applicare questi controlli del tempo di compilazione prima di C ++ 11.

Il Capitolo 2 (Sezione 2.1) di Modern C ++ Design di Andrei Alexanderscu implementa questa idea di asserzioni in fase di compilazione come questa

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

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

Confronta la macro STATIC_CHECK () e static_assert ()

STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");

-2

La static_assertpuò essere utilizzato per vietare l'uso della deleteparola chiave in questo modo:

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

Ogni sviluppatore C ++ moderno potrebbe volerlo fare se desidera utilizzare un garbage collector conservativo utilizzando solo classi e strutture che sovraccaricano l' operatore new per invocare una funzione che alloca memoria sull'heap conservativo del garbage collector conservativo che può essere inizializzato e istanziato invocando una funzione che lo fa all'inizio della mainfunzione.

Ad esempio, ogni moderno sviluppatore C ++ che desidera utilizzare il garbage collector conservativo Boehm-Demers-Weiser, all'inizio della mainfunzione scriverà:

GC_init();

E in ogni classe structsovraccarico in operator newquesto modo:

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

E ora che operator deletenon è più necessario, perché il garbage collector conservatore di Boehm-Demers-Weiser è responsabile sia di liberare che di deallocare ogni blocco di memoria quando non è più necessario, lo sviluppatore vuole vietare la deleteparola chiave.

Un modo è sovraccaricare in delete operatorquesto modo:

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

Ma questo non è raccomandato, perché il moderno sviluppatore C ++ saprà di aver invocato erroneamente il delete operatortempo di esecuzione, ma è meglio saperlo presto in fase di compilazione.

Quindi la migliore soluzione a questo scenario secondo me è usare il static_assertcome mostrato all'inizio di questa risposta.

Ovviamente questo può essere fatto anche con BOOST_STATIC_ASSERT, ma penso che static_assertsia meglio e dovrebbe essere preferito di più sempre.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.