Was macht static_assert und wofür würden Sie es verwenden?

117

Könnten Sie ein Beispiel geben, wo static_assert(...) ('C ++ 11') das Problem elegant lösen würde?

Ich bin mit der Laufzeit vertraut assert(...). Wann sollte ich es vorziehen, static_assert(...)regelmäßig zu sein assert(...)?

Auch boostda BOOST_STATIC_ASSERTdrin heißt etwas , ist es dasselbe wie static_assert(...)?

AraK
quelle
SIEHE AUCH: 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] für weitere Optionen. _MSG ist besonders schön, wenn Sie herausgefunden haben, wie man es benutzt.
KitsuneYMG

Antworten:

82

Aus dem Kopf ...

#include "SomeLibrary.h"

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

class UsingSomeLibrary {
   // ...
};

Angenommen, dies SomeLibrary::Versionwird als statische Konstante deklariert und nicht als #defined (wie in einer C ++ - Bibliothek zu erwarten).

Im Gegensatz dazu müssen Sie SomeLibraryIhren Code tatsächlich kompilieren , alles verknüpfen und die ausführbare Datei erst dann ausführen, um herauszufinden, dass Sie 30 Minuten damit verbracht haben, eine inkompatible Version von zu kompilierenSomeLibrary .

@Arak, als Antwort auf Ihren Kommentar: Ja, Sie können static_asserteinfach überall sitzen, wie es aussieht:

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: Fehler: statische Zusicherung fehlgeschlagen: "Foo :: bar ist zu klein :("
Mark Rushakoff
quelle
1
Ich bin ein wenig verwirrt. Können Sie static_asserteinen Kontext ohne Ausführung festlegen? Es scheint ein sehr schönes Beispiel zu sein :)
AraK
3
Ja, statische Asserts werden normalerweise so implementiert, dass sie ein Objekt erstellen, das nur definiert wird, wenn das Prädikat wahr ist. Dies würde nur eine globale machen.
GManNickG
Ich bin mir nicht sicher, ob dies die Beantwortung der ursprünglichen Frage in ihrer Gesamtheit qualifiziert, aber eine nette Demonstration
Matt Joiner
2
Diese Antwort liefert keine Details darüber , was ist der Unterschied zwischen Assertion von <cassert> und static_assert
bitek
11
@monocoder: Siehe den Absatz, der mit "Kontrast mit ..." beginnt. Kurz gesagt: assert überprüft seinen Zustand zur Laufzeit und static_assert überprüft seinen Zustand bei der Kompilierung. Wenn die von Ihnen behauptete Bedingung zur Kompilierungszeit bekannt ist, verwenden Sie static_assert. Wenn der Zustand erst bekannt wird, wenn das Programm ausgeführt wird, verwenden Sie assert.
Mike DeSimone
131

Statische Zusicherung wird verwendet, um Zusicherungen zur Kompilierungszeit zu machen. Wenn die statische Zusicherung fehlschlägt, wird das Programm einfach nicht kompiliert. Dies ist in verschiedenen Situationen nützlich, z. B. wenn Sie einige Funktionen per Code implementieren, die entscheidend davon abhängen, ob das unsigned intObjekt genau 32 Bit hat. Sie können eine statische Zusicherung wie folgt setzen

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

in Ihrem Code. Auf einer anderen Plattform mit unterschiedlich großem unsigned intTyp schlägt die Kompilierung fehl, wodurch der Entwickler auf den problematischen Teil des Codes aufmerksam gemacht und ihm empfohlen wird, ihn erneut zu implementieren oder zu überprüfen.

In einem anderen Beispiel möchten Sie möglicherweise einen ganzzahligen Wert als void *Zeiger auf eine Funktion übergeben (ein Hack, der jedoch manchmal nützlich ist), und Sie möchten sicherstellen, dass der ganzzahlige Wert in den Zeiger passt

int i;

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

Möglicherweise möchten Sie festlegen, dass dieser charTyp signiert ist

static_assert(CHAR_MIN < 0);

oder diese integrale Division mit negativen Werten rundet gegen Null

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

Und so weiter.

Laufzeit-Zusicherungen können in vielen Fällen anstelle von statischen Zusicherungen verwendet werden. Laufzeit-Zusicherungen funktionieren jedoch nur zur Laufzeit und nur dann, wenn die Kontrolle über die Zusicherung übergeht. Aus diesem Grund kann eine fehlerhafte Laufzeitzusicherung für längere Zeit unentdeckt ruhen.

Natürlich muss der Ausdruck in der statischen Zusicherung eine Konstante zur Kompilierungszeit sein. Es kann kein Laufzeitwert sein. Für Laufzeitwerte haben Sie keine andere Wahl, als das Gewöhnliche zu verwenden assert.

Ameise
quelle
3
Ist static_assert nicht erforderlich, um ein Zeichenfolgenliteral als zweiten Parameter zu haben?
Trevor Hickey
3
@ Trevor Hickey: Ja, das ist es. Aber ich habe nicht versucht, static_assertspeziell auf C ++ 11 zu verweisen . Mein static_assertobiges ist nur eine abstrakte Implementierung der statischen Behauptung. (Ich persönlich verwende so etwas im C-Code). Meine Antwort soll sich auf den allgemeinen Zweck statischer Zusicherungen und deren Unterschied zu Laufzeit-Zusicherungen beziehen.
Am
Im ersten Beispiel nehmen Sie an, dass eine Variable vom Typ keine Füllbits enthält unsigned int. Dies wird vom Standard nicht garantiert. Eine Variable vom Typ unsigned intkönnte legal 32 Bit Speicher belegen und 16 davon unbenutzt lassen (und somit wäre das Makro UINT_MAXgleich 65535). Die Art und Weise, wie Sie die erste statische Behauptung beschreiben (" unsigned intObjekt mit genau 32 Bit"), ist irreführend. Um Ihrer Beschreibung zu entsprechen, sollte diese Behauptung ebenfalls enthalten sein : static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS
@TrevorHickey nicht mehr (C ++ 17)
luizfls
13

Ich verwende es, um sicherzustellen, dass meine Annahmen über das Compilerverhalten, Header, Bibliotheken und sogar meinen eigenen Code korrekt sind. Zum Beispiel überprüfe ich hier, ob die Struktur korrekt auf die erwartete Größe gepackt wurde.

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

In einer Klasse Verpackung stdio.hist fseek(), habe ich einige Verknüpfungen mit genommen enum Originund prüfen, ob diese Verknüpfungen mit den Konstanten ausrichten , definiert durchstdio.h

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

Sie sollen lieber static_assertüber , assertwenn das Verhalten bei der Kompilierung definiert und zur Laufzeit nicht, wie die Beispiele , die ich oben gegeben habe. Ein Beispiel, in dem dies nicht der Fall ist, wäre die Überprüfung von Parametern und Rückkehrcodes.

BOOST_STATIC_ASSERTist ein Pre-C ++ 0x-Makro, das unzulässigen Code generiert, wenn die Bedingung nicht erfüllt ist. Die Absichten sind dieselben, obwohl sie static_assertstandardisiert sind und möglicherweise eine bessere Compilerdiagnose bieten.

Matt Joiner
quelle
9

BOOST_STATIC_ASSERT ist ein plattformübergreifender Wrapper für static_assert Funktionalität.

Derzeit verwende ich static_assert, um "Konzepte" für eine Klasse zu erzwingen.

Beispiel:

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

Dies führt zu einem Fehler bei der Kompilierung, wenn eine der oben genannten Bedingungen nicht erfüllt ist.

nurettin
quelle
3
Nachdem C ++ 11 nicht mehr verfügbar ist (und schon länger nicht mehr verfügbar ist), sollte static_assert von den neueren Versionen aller wichtigen Compiler unterstützt werden. Für diejenigen von uns, die nicht auf C ++ 14 warten können (das hoffentlich Vorlageneinschränkungen enthält), ist dies eine sehr nützliche Anwendung von static_assert.
Collin
7

Eine Möglichkeit besteht static_assertdarin, sicherzustellen, dass eine Struktur (dh eine Schnittstelle zur Außenwelt, z. B. ein Netzwerk oder eine Datei) genau die Größe hat, die Sie erwarten. Dies würde Fälle erfassen, in denen jemand ein Mitglied aus der Struktur hinzufügt oder ändert, ohne die Konsequenzen zu erkennen. Das static_assertwürde es abholen und den Benutzer alarmieren.

Greg Hewgill
quelle
3

In Ermangelung von Konzepten kann eine static_asserteinfache und lesbare Typprüfung zur Kompilierungszeit verwendet werden, beispielsweise in Vorlagen:

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

// ...
}
vladon
quelle
2

Dies beantwortet die ursprüngliche Frage nicht direkt, macht jedoch eine interessante Studie darüber, wie diese Überprüfungen der Kompilierungszeit vor C ++ 11 erzwungen werden können.

Kapitel 2 (Abschnitt 2.1) des modernen C ++ - Designs von Andrei Alexanderscu implementiert diese Idee solcher Behauptungen zur Kompilierungszeit

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

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

Vergleichen Sie das Makro STATIC_CHECK () und static_assert ()

STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");
Nachtwanderungen
quelle
-2

Mit static_assertkann die Verwendung des deleteSchlüsselworts folgendermaßen verboten werden:

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

Jeder moderne C ++ - Entwickler möchte dies möglicherweise tun, wenn er oder sie einen konservativen Garbage Collector verwenden möchte, indem er nur Klassen und Strukturen verwendet , die den Operator new überladen , um eine Funktion aufzurufen, die Speicher auf dem konservativen Heap des konservativen Garbage Collector zuweist kann initialisiert und instanziiert werden, indem eine Funktion aufgerufen wird, die dies am Anfang der mainFunktion tut .

Zum Beispiel mainschreibt jeder moderne C ++ - Entwickler, der den konservativen Garbage Collector Boehm-Demers-Weiser verwenden möchte, zu Beginn der Funktion:

GC_init();

Und in jeder classund structÜberlastung auf operator newdiese Weise:

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

Und jetzt, da das operator deletenicht mehr benötigt wird, weil der konservative Garbage Collector von Boehm-Demers-Weiser dafür verantwortlich ist, jeden Speicherblock freizugeben und freizugeben, wenn er nicht mehr benötigt wird, möchte der Entwickler das deleteSchlüsselwort verbieten .

Ein Weg ist das Überladen auf folgende delete operatorWeise:

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

Dies wird jedoch nicht empfohlen, da der moderne C ++ - Entwickler weiß, dass er fälschlicherweise die delete operatorOn- Runtime aufgerufen hat. Dies ist jedoch besser, wenn Sie dies bald bei der Kompilierung wissen.

Die beste Lösung für dieses Szenario ist meiner Meinung nach die Verwendung der static_assertam Anfang dieser Antwort gezeigten.

Natürlich kann das auch damit gemacht werden BOOST_STATIC_ASSERT, aber ich denke das static_assertist besser und sollte immer mehr bevorzugt werden.

user11962338
quelle