C++-Standardbibliothek utility

In der C++-Standardbibliothek,<utility>Die Header-Datei enthält einige praktische Hilfsklassen und -funktionen, die beim Schreiben von effizientem und lesbarem Code sehr nützlich sind.

utilityDer zentrale Wert der Bibliothek liegt in:

  • Bereitstellung grundlegender Datenstrukturen und Hilfsfunktionen
  • Vereinfachung der Implementierung gängiger Programmieraufgaben
  • Bereitstellung grundlegender Unterstützung für andere Komponenten der Standardbibliothek

utilityObwohl die Header-Datei klein ist, sind die Werkzeuge, die sie bereitstellt, sehr praktisch:

Komponenten/Funktionen Zweck Anwendungsfall
std::pair Zwei zusammengehörige Werte speichern Funktionen, die mehrere Werte zurückgeben, Map-Elemente
std::make_pair Bequemes Erstellen von Paaren Template-Typableitung, Code vereinfachen
std::swap Zwei Werte tauschen Algorithmusimplementierung, Sortieroperationen
std::move Move-Semantik aktivieren Ressourcenverwaltung, Leistungsoptimierung
std::forward Perfektes Forwarding Universelle Referenzen, Template-Programmierung

Kernkomponenten erklärt

std::pair: Schlüssel-Wert-Paar-Container

std::pairJautilitypair: die am häufigsten verwendete Komponente in der Bibliothek, mit der zwei Werte zu einem einzigen Objekt kombiniert werden.

Grundlegende Syntax

#include <utility>

// 创建 pair 对象的基本方式
std::pair<类型1, 类型2> 变量名(值1, 值2);

Beispiel

#include <iostream>
#include <utility>
#include <string>

int main() {
    // Methode 1: Direkte Initialisierung
    std::pair<int, std::string> student1(101, "Alice");
   
    // Methode 2: Verwendung der make_pair-Funktion (empfohlen)
    auto student2 = std::make_pair(102, "Bob");
   
    // Methode 3: Deduktionsleitfaden wird seit C++17 unterstützt
    std::pair student3(103, "Charlie");
   
    // Zugriff auf die Member von pair
    std::cout << "Studenten-ID: " << student1.first << ", Name: " << student1.second << std::endl;
   
    return 0;
}

Häufige Operationen mit pair

Beispiel

#include <utility>
#include <iostream>

void pairOperations() {
    // Ein pair erstellen
    std::pair<int, double> p1(10, 3.14);
    std::pair<int, double> p2(20, 2.71);
   
    // Vergleichsoperationen
    if (p1 < p2) {
        std::cout << "p1 ist kleiner als p2" << std::endl;
    }
   
    // Zuweisungsoperation
    p1 = p2;
   
    // Elementweise Zuweisung seit C++11 unterstützt
    int a;
    double b;
    std::tie(a, b) = p1;  // Den Wert von p1 a bzw. b zuweisen
   
    // C++17 strukturierte Bindung (prägnanter)
    auto [x, y] = p1;
    std::cout << "x = " << x << ", y = " << y << std::endl;
}

std::make_pair: praktische Erzeugungsfunktion

std::make_pairstd::make_pair: eine Template-Funktion, die Typen automatisch ableiten kann und das Erstellen von Paaren vereinfacht.

Beispiel

#include <utility>
#include <iostream>
#include <string>

void demonstrateMakePair() {
    // Automatische Typableitung, keine explizite Angabe von Template-Parametern nötig
    auto p1 = std::make_pair(42, "Hello");
    auto p2 = std::make_pair(3.14, true);
   
    // Besonders praktisch für die Verwendung in Containern
    std::vector<std::pair<int, std::string>> students;
    students.push_back(std::make_pair(101, "Alice"));
    students.push_back(std::make_pair(102, "Bob"));
   
    for (const auto& student : students) {
        std::cout << "Studenten-ID: " << student.first
                  << ", Name: " << student.second << std::endl;
    }
}

Hilfsfunktionen

std::swap: zwei Werte tauschen

std::swapWird verwendet, um die Werte zweier Objekte desselben Typs zu tauschen.

Beispiel

#include <utility>
#include <iostream>

void demonstrateSwap() {
    int a = 10, b = 20;
    std::cout << "Vor dem Tausch: a = " << a << ", b = " << b << std::endl;
   
    std::swap(a, b);
    std::cout << "Nach dem Tausch: a = " << a << ", b = " << b << std::endl;
   
    // Kann auch für benutzerdefinierte Typen verwendet werden (wenn Move-Semantik implementiert ist)
    std::string str1 = "Hallo", str2 = "Welt";
    std::swap(str1, str2);
    std::cout << "String-Austausch: " << str1 << " " << str2 << std::endl;
}

std::move: Unterstützung für Move-Semantik

std::moveWird verwendet, um ein Objekt in eine rvalue-Referenz umzuwandeln und dadurch Move-Semantik zu ermöglichen.

Beispiel

#include <utility>
#include <iostream>
#include <vector>

void demonstrateMove() {
    std::vector<int> v1 = {1, 2, 3, 4, 5};
    std::vector<int> v2;
   
    std::cout << "Vor dem Verschieben - v1 Größe: " << v1.size()
              << ", v2 Größe: " << v2.size() << std::endl;
   
    // Move-Semantik verwenden, um Ressourcenbesitz zu übertragen
    v2 = std::move(v1);
   
    std::cout << "Nach dem Verschieben - v1 Größe: " << v1.size()
              << ", v2 Größe: " << v2.size() << std::endl;
   
    // v1 befindet sich jetzt in einem gültigen, aber nicht spezifizierten Zustand
    // Normalerweise sollte v1 nicht erneut verwendet werden, außer es wird neu zugewiesen
}

std::forward: perfekte Weiterleitung

std::forwardWird verwendet, um Perfect Forwarding zu implementieren und die Wertkategorie von Argumenten zu erhalten.

Beispiel

#include <utility>
#include <iostream>

// Gewöhnliche Funktion – kann die Wertkategorie nicht beibehalten
template<typename T>
void normalFunction(T arg) {
    std::cout << "Gewöhnlicher Funktionsparameter" << std::endl;
}

// Funktion, die Perfect Forwarding verwendet
template<typename T>
void perfectForwardingFunction(T&& arg) {
    // Ursprüngliche Wertkategorie des Parameters beibehalten (lvalue oder rvalue)
    normalFunction(std::forward<T>(arg));
}

void demonstrateForward() {
    int x = 10;
   
    // Übergib einen lvalue
    perfectForwardingFunction(x);
   
    // Übergebe einen rvalue
    perfectForwardingFunction(20);
}

Dienstprogramme für Integer-Sequenzen (C++14)

C++14 führte Dienstprogramme für Integer-Sequenzen ein, die hauptsächlich für Template-Metaprogrammierung verwendet werden.

std::integer_sequence

Beispiel

#include <utility>
#include <iostream>

// Verwende eine Integer-Sequenz, um jeden Wert in der Sequenz auszugeben
template<typename T, T... Ints>
void print_sequence(std::integer_sequence<T, Ints...>) {
    // Verwende einen Fold-Ausdruck (C++17), um alle Werte auszugeben
    ((std::cout << Ints << " "), ...);
    std::cout << std::endl;
}

void demonstrateIntegerSequence() {
    // Erstelle eine Integer-Sequenz
    auto seq = std::integer_sequence<int, 1, 2, 3, 4, 5>();
    print_sequence(seq);
   
    // Verwende make_integer_sequence, um die Sequenz zu erzeugen
    auto seq2 = std::make_integer_sequence<int, 5>();
    print_sequence(seq2);  // Ausgabe: 0 1 2 3 4
}

Praxisnahe Anwendungsbeispiele

Beispiel 1: Funktion, die mehrere Werte zurückgibt

Beispiel

#include <utility>
#include <iostream>
#include <cmath>

// Funktion gibt mehrere Werte zurück: Berechnungsergebnis und Fehlercode
std::pair<double, bool> calculateSqrt(double number) {
    if (number < 0) {
        return std::make_pair(0.0, false);  // Fehlerfall
    }
    return std::make_pair(std::sqrt(number), true);  // Erfolgsfall
}

void multipleReturnValues() {
    auto result1 = calculateSqrt(16.0);
    if (result1.second) {
        std::cout << "Quadratwurzel: " << result1.first << std::endl;
    } else {
        std::cout << "Berechnungsfehler: Die Quadratwurzel einer negativen Zahl kann nicht gezogen werden" << std::endl;
    }
   
    auto result2 = calculateSqrt(-4.0);
    if (!result2.second) {
        std::cout << "Berechnungsfehler: Die Quadratwurzel einer negativen Zahl kann nicht gezogen werden" << std::endl;
    }
}

Fall 2: Anwendung in STL-Containern

Beispiel

#include <utility>
#include <map>
#include <iostream>
#include <string>

void mapWithPair() {
    // Jedes Element von std::map ist ein std::pair
    std::map<int, std::string> studentMap;
   
    // Schlüssel-Wert-Paar einfügen
    studentMap.insert(std::make_pair(101, "Alice"));
    studentMap.emplace(102, "Bob");  // Effizienterer Weg
   
    // Über die Map iterieren
    for (const auto& [id, name] : studentMap) {
        std::cout << "Studenten-ID: " << id << ", Name: " << name << std::endl;
    }
   
    // Element finden
    auto it = studentMap.find(101);
    if (it != studentMap.end()) {
        std::cout << "Gefundener Student: " << it->second << std::endl;
    }
}

Beispiel 3: Implementierung eines einfachen Wörterbuchs

Beispiel

#include <utility>
#include <vector>
#include <iostream>
#include <algorithm>

class SimpleDictionary {
private:
    std::vector<std::pair<std::string, std::string>> entries;
   
public:
    void addWord(const std::string& word, const std::string& meaning) {
        entries.emplace_back(word, meaning);
    }
   
    std::pair<bool, std::string> findMeaning(const std::string& word) {
        for (const auto& [w, m] : entries) {
            if (w == word) {
                return std::make_pair(true, m);
            }
        }
        return std::make_pair(false, "");
    }
   
    void printAll() {
        for (const auto& [word, meaning] : entries) {
            std::cout << word << ": " << meaning << std::endl;
        }
    }
};

void dictionaryExample() {
    SimpleDictionary dict;
    dict.addWord("Apfel", "eine Frucht");
    dict.addWord("Buch", "ein Gegenstand zum Lesen");
   
    auto result = dict.findMeaning("Apfel");
    if (result.first) {
        std::cout << "Bedeutung: " << result.second << std::endl;
    }
}

Bewährte Methoden und Überlegungen

1. Verwenden Sie auto, um die Paarerstellung zu vereinfachen

Beispiel

// Empfohlen: Verwenden Sie auto und make_pair
auto student = std::make_pair(101, "Alice");

// Nicht empfohlen: den Typ explizit angeben (ausführlicher)
std::pair<int, std::string> student(101, "Alice");

2. Bevorzuge emplace gegenüber insert

Beispiel

std::map<int, std::string> myMap;

// Empfohlen: emplace verwenden (effizienter)
myMap.emplace(1, "one");

// Nicht empfohlen: insert verwenden (erfordert das Erstellen eines temporären Objekts)
myMap.insert(std::make_pair(1, "one"));

3. Korrekte Verwendung der Move-Semantik

Beispiel

std::string createString() {
    std::string str = "a very large string";
    // Korrekt: move bei Rückgabe verwenden
    return str;  // Der Compiler optimiert automatisch, kein expliziter move nötig
}

void processString(std::string str) {
    // Strings behandeln
}

void usageExample() {
    std::string largeStr = "very large data";
   
    // Korrekt: move beim Übergeben verwenden
    processString(std::move(largeStr));
    // Hinweis: largeStr sollte nicht mehr verwendet werden
}

4. Verwendung von strukturierten Bindungen

Beispiel

// Strukturierte Bindungen werden seit C++17 unterstützt
std::pair<int, std::string> getStudent() {
    return {101, "Alice"};
}

void structuredBindingExample() {
    // Traditionelle Methode
    auto student = getStudent();
    int id = student.first;
    std::string name = student.second;
   
    // Moderne Methode (C++17)
    auto [id2, name2] = getStudent();  // Prägnanter und klarer
}
Weitere Erweiterungen