zurück zum Artikel

Softwareentwicklung: std::span in C++20: Weitere Details

Rainer Grimm

(Bild: SergioVas/Shutterstock)

In diesem Blogbeitrag geht es um das Objekt std::span, das sich auf eine zusammenhÀngende Folge von Objekten bezieht, und um ein persönliches Anliegen.

Heute möchte ich ĂŒber die nicht so bekannten Features eines std::span und die Gefahren schreiben. Außerdem geht es um ein persönliches Anliegen fĂŒr die Forschung zu ALS.

Modernes C++ – Rainer Grimm
Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tÀtig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und hÀufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschÀftigt er sich intensiv mit seiner Leidenschaft C++.

Ein std::span, manchmal auch View genannt, ist nie ein Besitzer. Dieser zusammenhĂ€ngende Speicher kann ein einfaches Array, ein Zeiger mit einer bestimmten GrĂ¶ĂŸe, ein std::array, ein std::vector oder ein std::string sein. Eine typische Implementierung besteht aus einem Zeiger auf sein erstes Element und einer GrĂ¶ĂŸe. Der Hauptgrund fĂŒr eine std::span<T> ist: Ein C-Array wird auf einen Zeiger auf seines erstes Element reduziert wird, wenn es an eine Funktion ĂŒbergeben wird. Dadurch geht die GrĂ¶ĂŸe des Arrays verloren. Dieser decay [1] ist ein typischer Grund fĂŒr Fehler in C/C++.

Im Gegensatz dazu leitet std::span<T> die GrĂ¶ĂŸe von zusammenhĂ€ngenden Folgen von Objekten automatisch ab.

// printSpan.cpp

#include <iostream>
#include <vector>
#include <array>
#include <span>

void printMe(std::span<int> container) {
    
    std::cout << "container.size(): " << container.size() << '\n';  // (4)
    for(auto e : container) std::cout << e << ' ';
    std::cout << "\n\n";
}

int main() {
    
    std::cout << std::endl;
    
    int arr[]{1, 2, 3, 4};              // (1)
    printMe(arr);
    
    std::vector vec{1, 2, 3, 4, 5};     // (2)
    printMe(vec);

    std::array arr2{1, 2, 3, 4, 5, 6}; // (3)
    printMe(arr2);
    
}

Das C-Array (1), std::vector (2) und das std::array (3) besitzen ints. Folglich enthĂ€lt std::span auch ints. In diesem einfachen Beispiel gibt es noch etwas Interessantes. FĂŒr jeden Container kann std::span seine GrĂ¶ĂŸe ableiten (4).

Dies war eine kurze Erinnerung an std::span. Alles Weitere steht in meinem vorherigen Artikel "C++20: GeschĂŒtzer Zugriff auf Sequenzen von Objekte mit std::span [2]".

Eine std::span kann einen static extent oder einen dynamic extent haben.

StandardmĂ€ĂŸig hat std::span einen dynamischen Extent:

template <typename T, std::size_t Extent = std::dynamic_extent>
class span;

Wenn ein std::span einen statischen Extent besitzt, ist seine GrĂ¶ĂŸe zur Compile-Zeit bekannt und Teil des Datentyps: std::span. Daher braucht ihre Implementierung nur einen Zeiger auf das erste Element der zusammenhĂ€ngenden Folge von Objekten.

Die Implementierung eines std::span mit einem dynamischen extent besteht aus einem Zeiger auf das erste Element und der GrĂ¶ĂŸe der zusammenhĂ€ngenden Folge von Objekten. Die GrĂ¶ĂŸe ist nicht Teil des std::span-Typs.

Das nÀchste Beispiel verdeutlicht die Unterschiede zwischen den beiden Arten von Spans.

// staticDynamicExtentSpan.cpp

#include <iostream>
#include <span>
#include <vector>

void printMe(std::span<int> container) {        // (3)  
    
    std::cout << "container.size(): " << container.size() << '\n';
    for (auto e : container) std::cout << e << ' ';
    std::cout << "\n\n";
}

int main() {

    std::cout << '\n';

    std::vector myVec1{1, 2, 3, 4, 5};        
    std::vector myVec2{6, 7, 8, 9};

    std::span<int> dynamicSpan(myVec1);          // (1)
    std::span<int, 4> staticSpan(myVec2);        // (2)

    printMe(dynamicSpan);
    printMe(staticSpan);

    // staticSpan = dynamicSpan;    ERROR        // (4)
    dynamicSpan = staticSpan;                    // (5) 

    printMe(staticSpan);                         // (6)

    std::cout << '\n';
    
}

dynamicSpan (1) hat einen dynamischen Extent, wĂ€hrend staticSpan (2) einen statischen hat. Beide std::spans geben ihre GrĂ¶ĂŸe in der Funktion printMe (3) zurĂŒck. Ein std::span mit statischen Extent kann einem std::span mit dynamischen extent zugewiesen werden, aber nicht umgekehrt. (4) wĂŒrde einen Fehler verursachen, aber (5) und (6) sind gĂŒltig.

Es gibt einen besonderen Anwendungsfall von std::span. Eine std::span kann ein konstanter Bereich von verÀnderbaren Elementen sein.

Der Einfachheit halber nenne ich einen std::vector und einen std::span Bereich. Ein std::vector modelliert einen verÀnderbaren Bereich von verÀnderbaren Elementen: std::vector. Wenn man diesen std::vector als const deklariert, modelliert er einen konstanten Bereich konstanter Objekte: const std::vector. Man kann keinen konstanten Bereich von verÀnderbaren Elementen modellieren. An dieser Stelle kommt std::span ins Spiel. Ein std::span modelliert einen konstanten Bereich von verÀnderbaren Objekten: std::span. Die folgende Abbildung verdeutlicht die Unterschiede zwischen (konstanten/verÀnderbaren) Bereichen und (konstanten/verÀnderbaren) Elementen.

// constRangeModifiableElements.cpp

#include <iostream>
#include <span>
#include <vector>

void printMe(std::span<int> container) {
    
    std::cout << "container.size(): " << container.size() << '\n';  
    for (auto e : container) std::cout << e << ' ';
    std::cout << "\n\n";
}

int main() {

    std::cout << '\n';

    std::vector<int> origVec{1, 2, 2, 4, 5};

    // Modifiable range of modifiable elements
    std::vector<int> dynamVec = origVec;           // (1)
    dynamVec[2] = 3;
    dynamVec.push_back(6);
    printMe(dynamVec);

    // Constant range of constant elements
    const std::vector<int> constVec = origVec;     // (2)
    // constVec[2] = 3;        ERROR
    // constVec.push_back(6);  ERROR
    std::span<const int> constSpan(origVec);       // (3)
    // constSpan[2] = 3;       ERROR

    // Constant range of modifiable elements
    std::span<int> dynamSpan{origVec};             // (4)
    dynamSpan[2] = 3;
    printMe(dynamSpan);

    std::cout << '\n';

}

Der Vektor dynamVec (1) ist ein Ă€nderbarer Bereich mit Ă€nderbaren Elementen. Diese Feststellung gilt nicht fĂŒr den Vektor constVec (2). constVec kann weder seine Elemente noch seine GrĂ¶ĂŸe Ă€ndern. constSpan (3) verhĂ€lt sich entsprechend. dynamSpan (4) modelliert den einzigartigen Anwendungsfall eines konstanten Bereichs mit verĂ€nderbaren Elementen.

Zum Schluss möchte ich noch auf zwei Gefahren hinweisen, die man beim Verwenden von std::span kennen sollte.

Die typischen Probleme von std::span sind zweierlei. Erstens sollte eine std::span nicht auf einen temporĂ€ren Bereich angewandt werden; zweitens sollte die GrĂ¶ĂŸe des zugrundeliegenden zusammenhĂ€ngenden Bereichs einer std::span nicht verĂ€ndert werden.

Ein std::span ist nie ein Besitzer und verlÀngert die Lebensdauer des zugrundeliegend Bereichs nicht. Daher sollte eine std::span nur auf lValues operieren. Die Verwendung einer std::span auf einem temporÀren Bereich ist undefiniertes Verhalten.

// temporarySpan.cpp

#include <iostream>
#include <span>
#include <vector>

std::vector<int> getVector() {                          // (2)
    return {1, 2, 3, 4, 5};
}

int main() {

     std::cout << '\n';
    
    std::vector<int> myVec{1, 2, 3, 4, 5};              // (1)
    std::span<int, 5> mySpan1{myVec};                  
    std::span<int, 5> mySpan2{getVector().begin(), 5};  // (3)

    for (auto v: std::span{myVec}) std::cout << v << " ";
    std::cout << '\n';
    for (auto v: std::span{getVector().begin(), 5}) std::cout << v << " ";  // (4)

     std::cout << "\n\n";
    
}

Die Verwendung eines std::span mit einem statischen Extent oder einer std::span mit einem dynamischen Extent auf dem lValue ist in Ordnung. Wenn ich vom lValue std::vector in (1) zu einem temporĂ€ren std::vector wechsle, der durch die Funktion getVector (2) erzeugt wird, besitzt das Programm undefiniertes Verhalten. Sowohl (3) als auch (4) sind ungĂŒltig. Beim AusfĂŒhren wird das undefinierte Verhalten sichtbar. Die Ausgabe in (4) stimmt nicht mit dem std::vector ĂŒberein, der von der Funktion getVector() erzeugt wird.

Ändern der GrĂ¶ĂŸe des zugrundeliegenden zusammenhĂ€ngenden Bereichs

Wenn man die GrĂ¶ĂŸe des zugrundeliegenden zusammenhĂ€ngenden Bereichs Ă€ndert, wird der zusammenhĂ€ngende Bereich möglicherweise neu allokiert und der std::span bezieht sich auf veraltete Daten.

std::vector<int> myVec{1, 2, 3, 4, 5};

std::span<int> sp1{myVec};

myVec.push_back(6);  // undefined behavior

In meinem nÀchsten Artikel tauche ich noch einmal in die Formatierungsbibliothek von C++20 ein.

Mein 3-BĂŒcher-Bundle Modern C++ Collection [3] ist jetzt fĂŒr die HĂ€lfte des Preises ($70 -> $35) verfĂŒgbar. Ich werde das Geld fĂŒr die ALS-Forschung spenden. Lass mich mit den Fakten beginnen.

Hier ist eine ErklÀrung der ALS-Ambulanz [4]:

ALS ist eines der “Sorgenkinder” der Medizin – seltene Krankheiten stehen nicht im sozialen Fokus. Es existiert keine generelle Finanzierung der ALS-Forschung. Deshalb ist die Behandlung von Menschen mit ALS erheblich unterfinanziert. Hierbei sind Spenden und externe UnterstĂŒtzung von großer Wichtigkeit.

Diese Unterfinanzierung ist durch die Ice Bucket Challenge [5]deutlich geworden.

Die Forschung braucht mehr Geld. Hier ist meine Idee. Lasst uns eine Spendenaktion starten!

Der Coupon fĂŒr das 3-BĂŒcher-Bundle Modern C++ Collection [6] fĂŒr die HĂ€lfte des Preises ($70 -> $35) ist bis zum 21. Januar gĂŒltig (inklusive).

Die folgenden drei BĂŒcher sind in diesem Bundle enthalten: The C++ Standard Library [7], Concurrency with Modern C++ [8]und C++20 [9].

Ich werde das gesamte Geld an die ALS-Ambulanz spenden. Die ALS-Ambulanz ist Teil der CharitĂ© und ist fĂŒhrend in der Erforschung und Behandlung von ALS in Europa. (rme [10])


URL dieses Artikels:
https://www.heise.de/-9592932

Links in diesem Artikel:
[1] https://en.cppreference.com/w/cpp/types/decay
[2] https://www.heise.de/blog/C-20-std-span-4892418.html
[3] https://leanpub.com/b/modernccollection/c/OsJPYun1ZJy7
[4] https://als-charite.de/
[5] https://en.wikipedia.org/wiki/Ice_Bucket_Challenge
[6] https://leanpub.com/b/modernccollection/c/OsJPYun1ZJy7
[7] https://leanpub.com/b/modernccollection#bundle-page-cpplibrary
[8] https://leanpub.com/b/modernccollection#bundle-page-concurrencywithmodernc
[9] https://leanpub.com/b/modernccollection#bundle-page-c20
[10] mailto:rme@ix.de