Que fait static_assert et à quoi l'utiliseriez-vous?

117

Pourriez-vous donner un exemple où static_assert(...)('C ++ 11') résoudrait le problème en question avec élégance?

Je connais le run-time assert(...). Quand devrais-je préférer static_assert(...)au régulier assert(...)?

En outre, boostil y a quelque chose qui s'appelle BOOST_STATIC_ASSERT, est-ce la même chose que static_assert(...)?

AraK
la source
VOIR AUSSI: 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] pour plus d'options. _MSG est particulièrement agréable une fois que vous avez compris comment l'utiliser.
KitsuneYMG

Réponses:

82

Du haut de ma tête...

#include "SomeLibrary.h"

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

class UsingSomeLibrary {
   // ...
};

En supposant que cela SomeLibrary::Versionsoit déclaré comme un const statique, plutôt que d'être #defined (comme on pourrait s'y attendre dans une bibliothèque C ++).

Contraste d'avoir à compiler en fait SomeLibraryet votre code, tout lien, et lancez l'exécutable seulement alors pour savoir que vous avez passé 30 minutes compilant une version incompatible SomeLibrary.

@Arak, en réponse à votre commentaire: oui, vous pouvez vous static_assertasseoir n'importe où, à première vue:

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: erreur: l'assertion statique a échoué: "Foo :: bar est trop petit :("
Mark Rushakoff
la source
1
Je suis un peu confus, pouvez-vous mettre static_assertdans un contexte de non-exécution? Cela semble un très bel exemple :)
AraK
3
Oui, les assertions statiques telles qu'elles sont sont généralement implémentées comme créant un objet qui n'est défini que si le prédicat est vrai. Cela ferait simplement un global.
GManNickG
Je ne suis pas sûr que cela puisse répondre à la question originale dans son intégralité, mais belle démonstration
Matt Joiner
2
Cette réponse ne fournit aucun détail sur la différence entre assert from <cassert> et static_assert
bitek
11
@monocoder: Voir le paragraphe commençant par "Contraste avec ...". En bref: assert vérifie sa condition à l'exécution, et static_assert vérifie sa condition à la compilation. Donc, si la condition que vous affirmez est connue au moment de la compilation, utilisez static_assert. Si la condition n'est pas connue avant l'exécution du programme, utilisez assert.
Mike DeSimone
131

L'assertion statique est utilisée pour faire des assertions au moment de la compilation. Lorsque l'assertion statique échoue, le programme ne se compile tout simplement pas. Ceci est utile dans différentes situations, comme, par exemple, si vous implémentez une fonctionnalité par code qui dépend de manière critique d'un unsigned intobjet ayant exactement 32 bits. Vous pouvez mettre une affirmation statique comme celle-ci

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

dans votre code. Sur une autre plate-forme, avec un unsigned inttype de taille différente, la compilation échouera, attirant ainsi l'attention du développeur sur la partie problématique du code et lui conseillant de le réimplémenter ou de le réinspecter.

Pour un autre exemple, vous voudrez peut-être passer une valeur intégrale en tant que void *pointeur vers une fonction (un hack, mais parfois utile) et vous voulez vous assurer que la valeur intégrale tiendra dans le pointeur

int i;

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

Vous souhaiterez peut-être que l'élément dont le chartype est signé

static_assert(CHAR_MIN < 0);

ou cette division intégrale avec des valeurs négatives arrondit vers zéro

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

Etc.

Dans de nombreux cas, les assertions d'exécution peuvent être utilisées à la place des assertions statiques, mais les assertions d'exécution ne fonctionnent qu'au moment de l'exécution et uniquement lorsque le contrôle passe sur l'assertion. Pour cette raison, une assertion d'exécution défaillante peut rester inactive, non détectée pendant de longues périodes.

Bien sûr, l'expression dans l'assertion statique doit être une constante au moment de la compilation. Il ne peut pas s'agir d'une valeur d'exécution. Pour les valeurs d'exécution, vous n'avez pas d'autre choix que d'utiliser l'ordinaire assert.

Fourmi
la source
3
Static_assert n'est-il pas REQUIS pour avoir une chaîne littérale comme deuxième paramètre?
Trevor Hickey
3
@Trevor Hickey: Oui, ça l'est. Mais je n'essayais pas de me référer static_assertspécifiquement à C ++ 11. Mon static_assertci-dessus est juste une implémentation abstraite de l'assertion statique. (J'utilise personnellement quelque chose comme ça dans le code C). Ma réponse est destinée à concerner l'objectif général des assertions statiques et leur différence par rapport aux assertions d'exécution.
Du
Dans le premier exemple, vous supposez qu'il n'y a pas de bits de remplissage dans une variable de type unsigned int. Ceci n'est pas garanti par la norme. Une variable de type unsigned intpourrait légalement occuper 32 bits de mémoire, laissant 16 d'entre eux inutilisés (et ainsi la macro UINT_MAXserait égale à 65535). Ainsi, la façon dont vous décrivez la première assertion statique (" unsigned intobjet ayant exactement 32 bits") est trompeuse. Pour correspondre à votre description, cette affirmation doit être incluse ainsi: static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS
@TrevorHickey n'est plus (C ++ 17)
luizfls
13

Je l'utilise pour m'assurer que mes hypothèses sur le comportement du compilateur, les en-têtes, les bibliothèques et même mon propre code sont correctes. Par exemple ici, je vérifie que la structure a été correctement compressée à la taille attendue.

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

Dans un emballage de classe stdio.h« s fseek()je, pris des raccourcis avec enum Originet vérifier que ces raccourcis sont alignés avec les constantes définies parstdio.h

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

Vous devriez préférer static_assertau assertmoment où le comportement est défini au moment de la compilation, et non au moment de l'exécution, comme les exemples que j'ai donnés ci-dessus. Un exemple où ce n'est pas le cas inclurait la vérification des paramètres et du code de retour.

BOOST_STATIC_ASSERTest une macro pré-C ++ 0x qui génère du code non conforme si la condition n'est pas satisfaite. Les intentions sont les mêmes, bien que static_assertstandardisées et peuvent fournir de meilleurs diagnostics du compilateur.

Matt Joiner
la source
9

BOOST_STATIC_ASSERTest un wrapper multi-plateforme pour la static_assertfonctionnalité.

Actuellement, j'utilise static_assert pour appliquer des "Concepts" à une classe.

exemple:

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

Cela provoquera une erreur de compilation si l'une des conditions ci-dessus n'est pas remplie.

Nurettin
la source
3
Maintenant que C ++ 11 est sorti (et est sorti depuis un certain temps), static_assert devrait être pris en charge par les versions les plus récentes de tous les principaux compilateurs. Pour ceux d'entre nous qui ne peuvent pas attendre C ++ 14 (qui, espérons-le, contiendra des contraintes de modèle), c'est une application très utile de static_assert.
Collin le
7

Une utilisation de static_assertpeut être de s'assurer qu'une structure (c'est-à-dire une interface avec le monde extérieur, comme un réseau ou un fichier) est exactement de la taille que vous attendez. Cela attraperait les cas où quelqu'un ajoute ou modifie un membre de la structure sans se rendre compte des conséquences. Le static_assertramasserait et alerterait l'utilisateur.

Greg Hewgill
la source
3

En l'absence de concepts, on peut utiliser static_assertpour une vérification de type simple et lisible à la compilation, par exemple, dans les modèles:

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

// ...
}
Vladon
la source
2

Cela ne répond pas directement à la question d'origine, mais constitue une étude intéressante sur la façon d'appliquer ces vérifications de temps de compilation avant C ++ 11.

Le chapitre 2 (section 2.1) de Modern C ++ Design par Andrei Alexanderscu implémente cette idée d'assertions au moment de la compilation comme ceci

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

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

Comparez la macro STATIC_CHECK () et static_assert ()

STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");
sentiers nocturnes
la source
-2

Le static_assertpeut être utilisé pour interdire l'utilisation du deletemot-clé de cette façon:

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

Chaque développeur C ++ moderne voudra peut-être le faire s'il souhaite utiliser un garbage collector conservateur en utilisant uniquement les classes es et struct s qui surchargent l' opérateur new pour appeler une fonction qui alloue de la mémoire sur le tas conservateur du garbage collector conservateur qui peut être initialisé et instancié en invoquant une fonction qui fait cela au début de la mainfonction.

Par exemple, chaque développeur C ++ moderne qui souhaite utiliser le garbage collector conservateur Boehm-Demers-Weiser écrira au début de la mainfonction:

GC_init();

Et dans chaque classet structsurcharge de operator newcette façon:

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

Et maintenant que le operator deleten'est plus nécessaire, parce que le garbage collector conservateur de Boehm-Demers-Weiser est responsable à la fois de libérer et de désallouer chaque bloc de mémoire lorsqu'il n'est plus nécessaire, le développeur veut interdire le deletemot - clé.

Une façon est de surcharger de delete operatorcette façon:

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

Mais ce n'est pas recommandé, car le développeur C ++ moderne saura qu'il / elle a invoqué par erreur delete operatorle temps d'exécution, mais il est préférable de le savoir bientôt au moment de la compilation.

Donc, la meilleure solution à ce scénario à mon avis est d'utiliser le static_assertcomme indiqué au début de cette réponse.

Bien sûr, cela peut également être fait avec BOOST_STATIC_ASSERT, mais je pense que static_assertc'est mieux et que cela devrait toujours être préféré.

user11962338
la source