C++ von den Grundlagen bis zum Profi — Folge 15
Verwalten Sie Ressourcen sicher, auch wenn Ausnahmen auftreten
Die in diesem Blog verfassten Artikel wurden mit dem Ziel erstellt, zu unterrichten. Außerdem können die Informationen im Artikel falsch sein, also seien Sie bitte vorsichtig.
Wenn nach dem Lesen der Datei und dem Zuweisen von Speicher ein Problem im Zwischencode auftritt, müssen Sie die bereits erhaltenen Ressourcen bereinigen. Wenn Sie den Bereinigungscode für jeden Rückgabe- und Ausnahmepfad manuell schreiben, kann es leicht passieren, dass er übersehen wird.
Wenn eine Ausnahme auftritt
Eine Ausnahme ist eine Möglichkeit, dem anrufenden Teilnehmer einen Fehler mitzuteilen, der am aktuellen Standort nicht behandelt werden kann.
Item LoadItem(int id)
{
if (id <= 0)
{
throw std::invalid_argument("id must be positive");
}
return ReadItemFromFile(id);
}
try
{
Item item = LoadItem(inputId);
}
catch (const std::exception& error)
{
std::cerr << error.what() << '\n';
}
Wenn throw ausgeführt wird, wird der Code unter der aktuellen Zeile übersprungen und ein passender Catch gefunden. Dabei wird der Destruktor des bereits erstellten lokalen Objekts aufgerufen.
Nicht alle Fehler erfordern Ausnahmen. Erwartbare Ergebnisse, wie beispielsweise ein Fehler bei der Artikelsuche, werden möglicherweise offensichtlicher als optional oder bool angezeigt.
Organisieren mit RAII
Durch die Bindung der Lebensdauer einer Ressource an die Lebensdauer eines Objekts besteht keine Notwendigkeit, normale Rückgabe- und Ausnahmepfade zu trennen.
std::string ReadConfig(const std::filesystem::path& path)
{
std::ifstream file(path);
if (!file)
{
throw std::runtime_error("config open failed");
}
std::string text(
std::istreambuf_iterator<char>{file},
std::istreambuf_iterator<char>{}
);
return text;
}
Selbst wenn eine Ausnahme auftritt, weil die Zeichenfolgenerstellung mittendrin fehlschlägt, schließt der Destruktor von ifstream die Datei. uniqueptr für dynamische Objekte, uniqueptr für Sperrenstd::lock_guard, Containerspeicher spielt die gleiche Rolle wie Vektor.
void UpdateInventory()
{
std::lock_guard<std::mutex> lock(inventoryMutex);
RebuildInventory();
}
Selbst wenn in RebuildInventory eine Ausnahme auftritt, gibt der Sperrdestruktor den Mutex frei. Es reduziert Fehler, die beim direkten Koppeln von Sperren und Entsperren auftreten können.
Ausnahme Sicherheit
Sie müssen darüber nachdenken, welchen Zustand Ihre Funktion garantiert, wenn eine Ausnahme auftritt.
- Grundgarantie: Es gibt keine Ressourcenlecks und Objekte bleiben nutzbar.
- Starke Garantie: Wenn der Vorgang fehlschlägt, bleibt der Zustand vor dem Aufruf erhalten.
- Garantiert keine Ausnahmen: Die Funktion löst keine Ausnahmen aus.
Es ist einfach, starke Garantien zu schaffen, indem der neue Zustand in einem temporären Objekt vervollständigt und am Ende ersetzt wird.
void Inventory::ReplaceItems(std::vector<Item> newItems)
{
items.swap(newItems);
}
Wenn beim Erstellen von Aufrufargumenten ein Fehler auftritt, werden vorhandene Elemente nicht geändert. Innerhalb der Funktion ersetzt die durch Swap vervollständigte neue Liste die bestehende Liste.
noexcept
Für Funktionen, die keine Ausnahmen auslösen,noexceptangezeigt werden kann.
Player(Player&& other) noexcept = default;
Player& operator=(Player&& other) noexcept = default;
Ein Vektor bestimmt, ob er bei der Neuzuordnung von Elementen sicher verschoben werden kann. Wenn der Verschiebungskonstruktor noexclusive ist, wählt er wahrscheinlich „Verschieben“ statt „Kopieren“.
Eine Ausnahme kann jedoch intern auftreten. Wenn Sie jedoch „noexclusive“ hinzufügen, wird in dem Moment, in dem die Ausnahme erlischt,std::terminateheißt. Es handelt sich dabei nicht um ein Schlüsselwort, das aus Leistungsgründen blind hinzugefügt wird, sondern um ein Versprechen, tatsächlich keine Ausnahme auszulösen.
Der Schlüssel zur Ausnahmesicherheit liegt darin, nicht viele Fänge zu schreiben. Ressourcen werden von RAII-Objekten verwaltet und Aufgaben werden in einer Reihenfolge organisiert, die den vorhandenen Status nicht zerstört, selbst wenn er fehlschlägt.