Bit-Manipulationen mit C++20
Mit diesem Artikel schließe ich meine Artikel zu Features der C++20-Bibliothek ab. Den Abschluss bilden die Klasse std::source_location und die Funktionen zur Bit-Manipulation.
Nun schließe ich meine Artikelserie zu Features der C++20-Bibliothek ab. Den Abschluss bilden die Klasse std::source_location und die Funktionen zur Bit-Manipulation.

std::source_location
std::source_location
bietet Informationen zum Sourcecode an. Diese umfassen den Dateinamen, die Zeilennummer und den Funktionsnamen. Diese Werte sind zum Debuggen, Loggen oder Testen sehr wertvoll. Damit ist die Klasse std::source_location
die deutliche bessere Alternative zu den vordefinierten Makros __FILE__
und __LINE__
in C++11. Das heißt natürlich, dass std::source_location
in C++20 zum Einsatz kommen sollte.
Die folgende Tabelle stellt das Interface von std::source_location
kompakt dar.

Der Aufruf std::source_location::current()
erzeugt eine neues source_location
-Objekt src.
Escstellt die Information des Aufrufers bereit. Zum jetzigen Zeitpunkt unterstützt noch kein C++-Compiler std::source_location
. Konsequenterweise ist das folgende Beispiel sourceLocation.cpp
direkt von der Online-Ressource cppreference.com/source_location [1]:
// sourceLocation.cpp
// from cppreference.com
#include <iostream>
#include <string_view>
#include <source_location>
void log(std::string_view message,
const std::source_location& location = std::source_location::current())
{
std::cout << "info:"
<< location.file_name() << ':'
<< location.line() << ' '
<< message << '\n';
}
int main()
{
log("Hello world!"); // info:main.cpp:19 Hello world!
}
Die Ausgabe des Programms ist Bestandteil des Sourcecodes.
Durch C++20 wird es sehr einfach, auf Bits oder Bit-Sequenzen zuzugreifen oder sie zu modifizieren.
Bit-Manipulationen
Dank des neuen Datentyps std::endia
n ist es einfach, die Byte-Reihenfolge eines skalaren Datentyps zu ermitteln.
Byte-Reihenfolge
- Die Byte-Reihenfolge kann big-endian order little-endian sein. Ersteres bedeutet, dass das höchstwertige Byte zuerst gespeichert wird, das zweite, dass das kleinstwertige Byte zuerst gespeichert wird.
- Ein skalarer Datentyp ist ein arithmetischer Datentyp, eine
enum
, ein Zeiger, ein Zeiger auf ein Mitglied oder einstd::nullptr_t
.
Die Klasse endian
bietet die Byte-Reihenfolge für skalare Datentypen an:
enum class endian
{
little = /*implementation-defined*/,
big = /*implementation-defined*/,
native = /*implementation-defined*/
};
- Wenn alle skalare Datentypen little-endian sind, dann ist der Wert von
std::endian::native std::endian::little.
- Wenn alle skalare Datentypen big-endian sind, dann ist der Wert von
std::endian::native std::endian::big.
Selbst Sonderfälle werden unterstützt:
- Wenn alle skalare Daten
sizeof 1
besitzen und damit die Byte-Reihenfolge irrelevant ist, sind die Werte aller Aufzählerstd::endian::little
,std::endian::big
undstd::endian::native
identisch. - Falls eine Plattform verschiedene Byte-Reihenfolgen verwendet, dann besitzt
std::endian::native
einen anderen Wert alsstd::endian::big
oderstd::endian::little
.
Das Ausführen des Programms getEndianness.cpp
auf einer x86-Architektur gibt mir die Antwort little-endian
zurück:
// getEndianness.cpp
#include <bit>
#include <iostream>
int main() {
if constexpr (std::endian::native == std::endian::big) {
std::cout << "big-endian" << '\n';
}
else if constexpr (std::endian::native == std::endian::little) {
std::cout << "little-endian" << '\n'; // little-endian
}
}
constexpr if
erlaubt es, Sourcecode bedingt zu kompilieren. Das heißt in dem konkreten Fall, dass die Kompilierung von der Byte-Reihenfolge der Architektur abhängt. Mehr Information zur Byte-Reihenfolge gibt die gleichnamige Wikipedia-Seite [2].
Bits oder Bit-Sequenzen manipulieren
Die folgenden Tabellen zeigen alle Funktionen im Überblick.

Die Funktionen benötigen mit Ausnahme der Funktion std::bit_cast
eine vorzeichenlose Ganzzahl (unsigned char
, unsigned short
, unsigned int
, unsigned long
oder unsigned long long
).

Die Funktion bit.cpp
zeigt die Anwendung der Funktionen:
// bit.cpp
#include <bit>
#include <bitset>
#include <iostream>
int main() {
std::uint8_t num= 0b00110010;
std::cout << std::boolalpha;
std::cout << "std::has_single_bit(0b00110010): "
<< std::has_single_bit(num) << '\n';
std::cout << "std::bit_ceil(0b00110010): "
<< std::bitset<8>(std::bit_ceil(num)) << '\n';
std::cout << "std::bit_floor(0b00110010): "
<< std::bitset<8>(std::bit_floor(num)) << '\n';
std::cout << "std::bit_width(5u): "
<< std::bit_width(5u) << '\n';
std::cout << "std::rotl(0b00110010, 2): "
<< std::bitset<8>(std::rotl(num, 2)) << '\n';
std::cout << "std::rotr(0b00110010, 2): "
<< std::bitset<8>(std::rotr(num, 2)) << '\n';
std::cout << "std::countl_zero(0b00110010): "
<< std::countl_zero(num) << '\n';
std::cout << "std::countl_one(0b00110010): "
<< std::countl_one(num) << '\n';
std::cout << "std::countr_zero(0b00110010): "
<< std::countr_zero(num) << '\n';
std::cout << "std::countr_one(0b00110010): "
<< std::countr_one(num) << '\n';
std::cout << "std::popcount(0b00110010): "
<< std::popcount(num) << '\n';
}
Die folgende Ausgabe erzeugt das Programm:

Das nächste Programm zeigt den Einsatz und die Ausgabe der Funktionen std::bit_floor
, std::bit_ceil
, std::bit_width
und std::bit_popcoun
t für die Zahlen 2 bis 7:
// bitFloorCeil.cpp
#include <bit>
#include <bitset>
#include <iostream>
int main() {
std::cout << std::endl;
std::cout << std::boolalpha;
for (auto i = 2u; i < 8u; ++i) {
std::cout << "bit_floor(" << std::bitset<8>(i) << ") = "
<< std::bit_floor(i) << '\n';
std::cout << "bit_ceil(" << std::bitset<8>(i) << ") = "
<< std::bit_ceil(i) << '\n';
std::cout << "bit_width(" << std::bitset<8>(i) << ") = "
<< std::bit_width(i) << '\n';
std::cout << "bit_popcount(" << std::bitset<8>(i) << ") = "
<< std::popcount(i) << '\n';
std::cout << std::endl;
}
std::cout << std::endl;
}

Wie geht's weiter?
Neben Coroutinen hat C++20 viele weitere Concurrency-Features anzubieten. Die neuen atomaren Variablen gibt es für Gleitkommazahlen und Smart Pointer. Atomare Variablen erlauben es darüber hinaus, auf Benachrichtigungen zu warten. Zur Koordination von Threads wird C++20 um Sempaphoren, Latches und Barriers erweitert. Zusätzlich wurde std::thread
mit std::jthread
verbessert. Die Ausführung eines std::jthread
kann unterbrochen werden. Zusätzlich ruft ein std::jthread
automatisch join
in seinem Destruktor auf.
( [3])
URL dieses Artikels:
https://www.heise.de/-4981203
Links in diesem Artikel:
[1] https://en.cppreference.com/w/cpp/utility/source_location
[2] https://de.wikipedia.org/wiki/Byte-Reihenfolge
[3] mailto:rainer@grimm-jaud.de
Copyright © 2020 Heise Medien