Wie C++17 von Boost-Bibliotheken profitiert, Teil Eins

Wie C++17 von Boost-Bibliotheken profitiert, Teil Eins

Heute haben wir einen Gastbeitrag von Bartlomiej Filipek. Bartek ist C++ Programmierer, Blogger und Autor. Sie können ihn auf LinkedIn oder seinem Blog finden und auch sein Buch lesen.

Im heutigen Artikel zeige ich Ihnen kampferprobte Features aus den bekannten Boost-Bibliotheken, die in C++17 angepasst wurden.

Mit der wachsenden Anzahl von Elementen in der Standardbibliothek, unterstützt durch die Erfahrung von Boost, können Sie noch flüssigeren C++-Code schreiben.

Lesen Sie weiter und erfahren Sie mehr über die coolen Dinge in C++.

Die Serie

  • Wie C++17 von Boost-Bibliotheken profitiert, Teil Eins (dieser Beitrag)
  • Wie C++17 von Boost-Bibliotheken profitiert, Teil 2

Einleitung

Vor einiger Zeit sah ich bei Fluent C++ eine Sammlung von Artikeln über Boost-Algorithmen:

  • Die BooSTL-Algorithmen:Boost-Algorithmen, die die STL erweitern (1/3)
  • Die BooSTL-Algorithmen:Boost-Algorithmen, die die STL erweitern (2/3)
  • Die BooSTL-Algorithmen:Boost-Algorithmen, die die STL erweitern (3/3)

In der Serie beschrieb Jonathan verschiedene Sortieralgorithmen, erweiterte Partitionierung, Mustersuche und einige andere. Mir ist aufgefallen, dass viele Elemente aus Boost jetzt Teil der Standardbibliothek sind, was mich dazu inspiriert hat, dieses Thema anzugehen.

Wie Sie wissen, bieten uns Boost-Bibliotheken eine Vielzahl praktischer Algorithmen, Typen und Funktionen, die wir in der Standardbibliothek nicht haben. Viele Funktionalitäten wurden in Core C++ „portiert“. In C++11 haben wir beispielsweise std::regex , Threading und intelligente Zeiger.

In diesem Zusammenhang können wir Boost als Testfeld behandeln, bevor wir zur Standardbibliothek übergehen.

Als ich mein Buch über C++17 geschrieben habe, ist mir aufgefallen, dass eine große Anzahl von Elementen von Boost in den neuen Standard „umgezogen“ wurden.

Zum Beispiel:

  • Wortschatztypen, std::variant , std::any , std::optional
  • string_view
  • Sucher – Boyer Moore und Boyer Moore Horspool
  • std::filesystem
  • spezielle mathematische Funktionen
  • Vorlagenverbesserungen

Die gute Nachricht ist, dass Sie nur kleine Teile von Boost wie boost::variant verwendet haben oder boost::optional , können Sie jetzt fast denselben Code verwenden und in die Standardbibliothekstypen konvertieren (std::variant und std::optional ).

Werfen wir einen Blick auf diese Bereiche, und das erste Thema ist „Wortschatztypen“.

Wortschatztypen

Ausdrucksstarken Code schreiben zu können, ist eine überzeugende Fähigkeit. Manchmal bietet die Verwendung von nur integrierten Typen diese Optionen nicht. Sie können beispielsweise eine Zahl einrichten und ihr „NOT_NUMBER“ zuweisen oder Werte von -1 als Nulleinträge behandeln. Als „ultimative Lösung“ könnten Sie sogar einen Zeiger verwenden und nullptr behandeln als null … aber wäre es nicht besser, einen expliziten Typ aus dem Standard zu haben?

Wie wäre es alternativ damit, mehrere alternative Typen in einem einzigen Objekt zu speichern? Sie können es mit Gewerkschaften im C-Stil versuchen, aber sie sind schwer zu verwenden und auf sehr niedrigem Niveau … und verursachen Probleme. Wie wäre es mit einem Typ, der mehrere Alternativen speichern kann … oder einem Objekt, das jeden beliebigen Typ speichern kann?

Wenn Sie Boost verwenden, sind Sie wahrscheinlich auf Typen wie boost::optional, boost::variant gestoßen und boost::any .

Anstatt -1 als „Nullzahl“ zu behandeln, nutzen Sie optional<int> – Wenn optional „leer“ ist, haben Sie keine Nummer. Ganz einfach.

Alternativ variant<string, int, float> ist der Typ, mit dem Sie drei mögliche Typen speichern und zur Laufzeit zwischen ihnen wechseln können.

Schließlich gibt es noch einen, der wie ein var-Typ in dynamischen Sprachen ist; Es kann jeden Typ speichern und dynamisch ändern. Es könnte int sein, und später können Sie es in string umwandeln.

Schauen wir uns etwas Code an:

std::optional

Der erste ist std::optional :

template <typename Map, typename Key>
std::optional<typename Map::value_type::second_type> TryFind(const Map& m, const Key& k) {
    auto it = m.find(k);
    if (it != m.end())
        return std::make_optional(it->second);
    return std::nullopt;
}

TryFind gibt optional den in der Karte gespeicherten Wert oder nullopt zurück . Siehe Demo @Wandbox.

Sie können es folgendermaßen verwenden:

std::map<std::string, int> mm { {"hello", 10}, { "super", 42 }};
auto ov = TryFind(mm, "hello");

// one:
std::cout << ov.value_or(0) << '\n';

// two:
if (ov)
    std::cout << *ov << '\n';

Wenn die optionale ov einen Wert enthält, können wir über .value() darauf zugreifen Mitgliedsfunktion oder operator* . Im obigen Code haben wir eine andere Alternative verwendet, nämlich value_or() Funktion, die den Wert zurückgibt, falls vorhanden, oder den übergebenen Parameter zurückgibt.

std::variante

std::optional speichert einen Wert oder nichts, wie wäre es also mit dem Speichern mehrerer Typen in einem sicheren Vereinigungstyp?

Hier ist ein Beispiel:

std::variant<int, float, std::string> TryParseString(std::string_view sv) {
    // try with float first
    float fResult = 0.0f;
    const auto last = sv.data() + sv.size();
    const auto res = std::from_chars(sv.data(), last, fResult);
    if (res.ec != std::errc{} || res.ptr != last) {
        // if not possible, then just assume it's a string
        return std::string{sv};
    }
    // no fraction part? then just cast to integer
    if (static_cast<int>(fResult) == fResult)
        return static_cast<int>(fResult);
    return fResult;
}

std::variant kann verwendet werden, um verschiedene Typen als Parsing-Ergebnis zu speichern. Ein häufiger Anwendungsfall ist das Parsen der Befehlszeile oder einer Konfigurationsdatei. Die Funktion TryParseString Nimmt eine String-Ansicht und versucht dann, sie in Float, Int oder String zu parsen. Wenn der Gleitkommawert keinen Bruchteil hat, speichern wir ihn als Ganzzahl. Ansonsten ist es Float. Wenn die numerische Konvertierung nicht durchgeführt werden kann, kopiert die Funktion den String.

Um auf den in einer Variante gespeicherten Wert zugreifen zu können, müssen Sie zunächst den aktiven Typ kennen. Hier ist ein Code, der zeigt, wie es geht und den Rückgabewert von TryParseString verwendet :

const auto var = TryParseString("12345.98");
try {
    if (std::holds_alternative<int>(var))
        std::cout << "parsed as int: " << std::get<int>(var) << '\n';
    else if (std::holds_alternative<float>(var))
        std::cout << "parsed as float: " << std::get<float>(var) << '\n';
    else if (std::holds_alternative<string>(var))
        std::cout << "parsed as string: " << std::get<std::string>(var) << '\n';
}
catch (std::bad_variant_access&) {
    std::cout << "bad variant access...\n";
}

Die Hauptidee ist die Verwendung von std::holds_alternative() Dadurch können wir überprüfen, welcher Typ vorhanden ist. Variante bietet auch den .index() Mitgliedsfunktion, die eine Zahl von 0 bis zur maximalen Anzahl gespeicherter Typen zurückgibt.

Aber eine der coolsten Anwendungen ist ein Ding namens std::visit() .

Mit dieser neuen Funktion können Sie eine Variante übergeben und den aktiv gespeicherten Typ aufrufen. Dazu müssen Sie einen Funktor bereitstellen, der den Aufrufoperator für alle möglichen Typen in der angegebenen Variante enthält:

struct PrintInfo {
    void operator()(const int& i) const    { cout << "parsed as int" << i << '\n'; }
    void operator()(const float& f) const  { cout << "parsed as float" << f << '\n'; }
    void operator()(const string& s) const { cout << "parsed as str" << s << '\n'; }
};

auto PrintVisitorAuto = [](const auto& t) { std::cout << t << '\n'; };
const auto var = TryParseString("Hello World");
std::visit(PrintVisitorAuto , var);
std::visit(PrintInfo{}, var);

Im obigen Beispiel haben wir zwei „Arten“ von Besuchern verwendet. Der erste – PrintInfo ist eine Struktur, die alle Überschreibungen für den Anrufoperator bereitstellt. Wir können es verwenden, um mehr Informationen über den angegebenen Typ anzuzeigen und einzigartige Implementierungen durchzuführen. Die andere Version – PrintVisitorAuto – nutzt generische Lambdas, was praktisch ist, wenn die Implementierung für alle Typen gleich ist.

Sie können das Überlastungsmuster auch in einem separaten Blogbeitrag nachlesen. Dadurch können Sie alle Lambdas lokal an einer Stelle schreiben, an der std::visit() heißt:Bartek’s Coding Blog:2 Lines Of Code and 3 C++17 Features – The Overload Pattern.

std::any

std::any ist wahrscheinlich der am wenigsten bekannte Vokabulartyp, und ich denke, es gibt nicht viele Anwendungsfälle für einen so flexiblen Typ. Es ist fast wie var von JavaScript, da es alles enthalten kann.

Eine kleine Demo von std::any (stammt aus dem Vorschlag N1939):

struct property {
    property();
    property(const std::string &, const std::any &);
    std::string name;
    std::any value;
};
typedef std::vector<property> properties;

Mit einer solchen Eigenschaftsklasse können Sie jeden Typ speichern. Wenn Sie jedoch die Anzahl der möglichen Typen einschränken können, ist es besser, std::variant zu verwenden, da es schneller als std::any ausgeführt wird (keine zusätzliche dynamische Speicherzuweisung erforderlich).

Mehr über `optional`, `variant` und any

Wenn Sie mehr über die Vokabeltypen erfahren möchten, können Sie separate Artikel lesen, die ich in meinem Blog geschrieben habe:

  • unter Verwendung von std::optional,
    • Und auch der letzte Beitrag bei fluentcpp über ausdrucksstarke nullable-Typen:hier und hier.
  • unter Verwendung von std::variant,
  • mit std::any.

std::string_view – nicht besitzender String

std::string_view ist ein nicht besitzender Blick auf die zusammenhängende Zeichenkette. Es ist seit einigen Jahren in Boost bereit (siehe boost utils string_view). Soweit ich weiß, waren ihre Schnittstellen etwas anders, aber jetzt ist die Boost-Version C++17-konform.

Konzeptionell string_view besteht aus einem Zeiger auf die Zeichenfolge und der Größe:

struct BasicCharStringView {
    char* dataptr;
    size_t size;
};

Sie fragen sich vielleicht, was an std::string_view einzigartig ist ?

Zuerst string_view ist ein natürlicher Ersatz für char* Argumente. Wenn Ihre Funktion const char* benötigt und führt dann einige Operationen darauf aus, dann können Sie auch view verwenden und von einer netten string-ähnlichen API profitieren.

Zum Beispiel:

size_t CStyle(const char* str, char ch) {
    auto chptr = strchr(str, ch);
    if (chptr != nullptr)
        return strlen(str) + (chptr - str);
    return strlen(str);
}

size_t CppStyle(std::string_view sv, char ch) {
    auto pos = sv.find(ch);
    if (pos != std::string_view::npos)
        return sv.length() + pos;
    return sv.length();
}

// use:
std::cout << CStyle("Hello World", 'X') << '\n';
std::cout << CppStyle("Hello World", 'X') << '\n';

Siehe den Code @Wandbox

Darüber hinaus gibt es, wie Sie vielleicht wissen, viele String-ähnliche Klassenimplementierungen. CString , QString , etc… und wenn Ihr Code viele Typen handhaben muss, string_view könnte helfen. Diese anderen Typen können Zugriff auf den Datenzeiger und die Größe bieten, und dann können Sie einen string_view erstellen Objekt.

Ansichten können auch hilfreich sein, wenn Sie an großen Saiten arbeiten und kleinere Abschnitte schneiden und schneiden. Zum Beispiel beim Parsen von Dateien:Sie können Dateiinhalte in einen einzigen std::string laden Objekt und verwenden Sie dann Ansichten, um die Verarbeitung durchzuführen. Dies könnte einen netten Leistungsschub darstellen, da keine zusätzlichen Kopien von Strings benötigt werden.

Es ist auch wichtig, sich daran zu erinnern, dass, da string_view die Daten nicht besitzt und möglicherweise auch nicht nullterminiert ist, einige Risiken mit seiner Verwendung verbunden sind:

  • Aufpassen der (nicht)nullterminierten Strings – string_view darf am Ende der Zeichenfolge nicht NULL enthalten. Auf so einen Fall muss man also vorbereitet sein.
    • Problematisch beim Aufrufen von Funktionen wie atoi, printf, die nullterminierte Strings akzeptieren
  • Referenzen und temporäre Objekte – string_view besitzt den Speicher nicht, daher müssen Sie beim Arbeiten mit temporären Objekten sehr vorsichtig sein.
    • Bei Rückgabe von string_view aus einer Funktion
    • Speichere string_view in Objekten oder Behältern.

Eine gute Zusammenfassung der Saitenansichten finden Sie im Blogbeitrag von Marco Arena:string_view odi et amo.

beginnt_mit/endet_mit neuen Algorithmen 

C++20-Info:Eine weitere gute Nachricht ist, dass starts_with() /ends_with() Algorithmen von Boost sind jetzt Teil von C++20… und viele Compiler haben sie bereits implementiert. Sie sind beide für string_view verfügbar und std::string .

Zusammenfassung

Ich hoffe, ich habe Ihnen mit diesem Blogbeitrag weitere Anreize gegeben, mit C++17 zu beginnen :). Und dies ist nur der erste Teil der Serie!

Der letzte C++-Standard bietet nicht nur viele Sprachfeatures (wie if constexpr , strukturierte Bindungen, Faltungsausdrücke …), aber auch eine breite Palette von Dienstprogrammen aus der Standardbibliothek. Sie können jetzt viele Wortschatztypen verwenden:variant , optional , any . Verwenden Sie Zeichenfolgenansichten und sogar eine wichtige Komponente:std::filesystem (siehe nächster Artikel). Alles ohne auf externe Bibliotheken verweisen zu müssen.

Dies war nur der erste Artikel einer kleinen Serie. Bitte warten Sie auf einen weiteren Blogbeitrag, in dem ich Ihnen weitere Tools zeige, die auch in C++17 verfügbar sind:std::filesystem , Suchfunktionen, mathematische Funktionen und mehr!

Du bist dran

  • Welche Funktionen von Boost verwenden Sie am liebsten?
  • Vielleicht werden sie auch in den Standard integriert?
  • Haben Sie Boost-Code in C++17 (und den entsprechenden Funktionssatz) portiert?

Teilen Sie Ihre Erfahrungen in Kommentaren.