CPP2019SSF08

Aus Verteilte Systeme - Wiki
Zur Navigation springen Zur Suche springen

Feature: static_assert

  • Eingeführt in: C++ 11
  • Deprecated seit: -
  • Nicht mehr im Standard seit: -

Syntax

// since C++11:
static_assert ( bool_constexpr , message )

// since C++ 17
static_assert ( bool_constexpr )
  • bool_constexpr = Ein Ausdruck, der sich während des Kompilieren zu true oder false auswerten lässt. Genauere Informationen zu constexpr: constexpr Wiki Seite
  • message = Ein String, der bei einem Fehler (das heißt constexpr wird mit false ausgewertet) in der Konsole ausgegeben wird. Dieser darf nur Literale enthalten, keine Variablen, da diese beim Kompilieren nicht ausgewertet werden können.
  • Ab C++ 17 ist der Parameter "message" optional.

Motivation für die Einführung

Um guten und vor allem lauffähigen Code zu schreiben, ist es hilfreich, bestimmte Annahmen zu überprüfen. So eine Annahme könnte unter anderem die Größe eines Datentypes sein, zum Beispiel das int 4 Bytes groß ist. Wäre die Annahme nicht erfüllt, würde es zu Problemen im weiteren Programmverlauf kommen. Daher kann man diese mit Assertions prüfen und im Fehlerfall eine Fehlermeldung ausgeben sowie das Programm beenden lassen. Diese werden jedoch nur bei Laufzeit ausgewertet, was zu Performance-Einbussen führt und Speicherplatz belegt. Aus diesem Grund wurde mit C++11 das Feature static_assert eingeführt, was es erlaubt, fehlerhafte Annahmen schon beim Kompilieren festzustellen und gegebenenfalls zu beheben. Dadurch kommt es zu keinen Performance-Einbussen zur Laufzeit. Außerdem kann man sich bereits während der Ausführung des Programms sicher sein, dass diese Annahmen zutreffen, da ansonsten kein ausführbarer Code entstehen würde.

Use Cases

Static Assertions können verwendet werden, um bestimmte Annahmen beziehungsweise Bedingungen schon während des Kompilierens zu überprüfen. Diese können zum Beispiel die Größe von bestimmten Datentypen sein. Wenn diese von vornherein nicht erfüllt sind, könnte es zu Problemen im weiteren Programmverlauf kommen. Static Assertions können in verschiedenen Gültigkeitsbereichen verwendet werden, für weitere Details siehe #Code-Beispiele.

Vorherige Lösungsansätze

Ansatz : Assertions zur Laufzeit

Bevor es Static Assertions gab, wurden "normale" Assertions benutzt, die zur Laufzeit des Programms ausgewertet wurden, die jedoch die Performance beeinträchtigen. Ein Beispiel für eine Assertion wäre:

assert(index >= 0 && index <= 9);

Wenn die Bedingung zur Laufzeit nicht erfüllt ist, wird lediglich der Inhalt in den Klammern ausgegeben:

Output:
Assertion failed: index >= 0 && index <=9

Diese Fehlermeldung ist nicht sehr aussagekräftig, daher wurde mit Hilfe eines Work-Arounds versucht, eine aussagekräftigere Fehlermeldung auszugeben:

assert(found && "Car could not be found in database");

Wenn hier die Bedingung zur Laufzeit nicht erfüllt ist, wird wieder lediglich der Inhalt in den Klammern ausgegeben:

Output:
Assertion failed: found && "Car could not be found in database"

Durch die Verundung wird hier jedoch auch der Text "Car could not be found in database" mit ausgegeben, sieht aber nicht sehr schön aus. Besonders wenn die Bedingung sehr lang wird, ist diese auch sehr unübersichtlich.

Ein weiteres Problem dieser Assertions ist es, dass wenn das Makro NDEBUG definiert ist, diese Bedingungen nicht ausgewertet werden. Daher sollte man hier niemals Bedingungen verwenden, die dringend für einen korrekten Verlauf des Programmes benötigt werden, da man diese eigentlich nur zu Testzwecken benutzt und nicht in dem Code, der produktiv ausgeführt wird.

Ansatz : Static Assertions mit #error

Im Vergleich zum ersten Lösungsansatz, der Assertions zur Laufzeit auswertet, gab es auch einen Ansatz, der Static Assertions ermöglichen sollte. Ein Beispiel wäre:

#include <iostream> 
using namespace std; 
#if defined(_APPLE) && defined(_LINUX)
#  error Conflicting operating system option selected, choose one.
#endif
int main() { 
    return 0; 
}

Beim Kompilieren würde dann ausgegeben werden:

Output:
Conflicting operating system option selected, choose one.

Hier wird #error benutzt, um zu überprüfen, ob bestimmte Makros definiert sind. Das Problem an diesem Ansatz ist, dass er komplexere Überprüfungen wie zum Beispiel die Größe eines Datentyps nicht ausführen kann.

Code-Beispiele

Static Asserts können in 3 verschiedenen Gültigkeitsbereichen (Scope) benutzt werden. Diese sind global, innerhalb einer Klasse und innerhalb eines Anwendungsblock.

Beispiel: Block Scope

Der Block Scope ist der Gültigkeitsbereich innerhalb eines Anwendungsblocks. Das Assert wird vom Compiler ausgewertet, sobald die Methode kompiliert wird.

int main() {
	static_assert(sizeof(int) == sizeof(long int),"int and long int must be of the same length.");
	//weitere Anweisungen bzw. Programm-Code
}

Wenn beim Kompilieren dieses Beispiels die Größe von int und long int nicht identisch sind, wird die Fehlermeldung "int and long int must be of the same length." ausgegeben und das Kompilieren abgebrochen. Trifft die Bedingung zu, so arbeitet der Compiler einfach weiter.

Beispiel: Class Scope

Der Class Scope ist der Gültigkeitsbereich innerhalb einer Klasse. Das Assert wird vom Compiler ausgewertet, sobald die Klasse kompiliert wird.

#include <iostream> 
using namespace std; 
  
template <class T, int Size> 
class Vector { 
    static_assert(Size < 3, "Vector size is too small!");
    T m_values[Size]; 
}; 
  
int main() { 
    Vector<int, 4> four; // This will work 
    Vector<short, 2> two; // This will fail  
    return 0; 
}

In diesem Beispiel wird ein Template mit einer Klasse und einer Größe deklariert. Beim Erstellen einer Instanz von Vector wird per Assert die Größe des Vektors geprüft. Ein Vektor ist nur valide, wenn er mindestens 3 Werte enthält, daher wird eine Fehlermeldung ausgegeben, sollte die Größe kleiner als 3 sein. In der main werden zwei Instanzen der Klasse Vector erzeugt, einmal mit der Größe 4 und einmal mit 2. Beim ersten Vektor gäbe es keine Probleme, da die Annahme über die Größe erfüllt wird, wohingegen der zweite Vektor einen Fehler ausgeben würde, da er die Annahme nicht erfüllt.

Output:
error: static assertion failed: Vector size is too small!

Beispiel: Namespace Scope

Der Namescape Scope ist der globale Gültigkeitsbereich. Diese Asserts enthalten Bedingungen über Annahmen, die der Compiler schon zu Beginn weiß, sodass diese direkt vom Compiler ausgewertet werden.

#include <iostream> 
using namespace std; 
static_assert(sizeof(void *) == 4, "64-bit code generation is not supported.");

int main() {
	//weitere Anweisungen bzw. Programm-Code
}

In diesem Beispiel kennt der Compiler die Größe eines Pointers schon zu Beginn des Kompilierens, daher wird das Assert direkt ausgewertet. Sollte die Größenbedingung nicht erfüllt sein, wird die Fehlermeldung "64-bit code generation is not supported." ausgegeben und das Kompilieren abgebrochen. Trifft die Bedingung zu, so arbeitet der Compiler einfach weiter.

Quellen

https://en.cppreference.com/w/cpp/language/static_assert
https://www.systemic-engineering.ch/blog/28-static-assertion
https://www.geeksforgeeks.org/understanding-static_assert-c-11/
https://docs.microsoft.com/en-us/cpp/cpp/static-assert?view=vs-2019

Präsentation

Medium:STATIC_ASSERTION.pdf