Własne hierarchie wyjątków
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 82 z 104.
Chociaż biblioteka standardowa udostępnia przydatne klasy wyjątków, takie jak std::runtime_error, aplikacje używane w rzeczywistych warunkach często zyskują na tworzeniu własnych hierarchii wyjątków. Definiując własne klasy wyjątków dziedziczące po wyjątkach standardowych, możesz tworzyć typy błędów specyficzne dla danej domeny, które zawierają dodatkowy kontekst i umożliwiają precyzyjniejszą obsługę błędów.
Klasa własnych wyjątków zazwyczaj dziedziczy po std::exception lub jednej z jej klas pochodnych i nadpisuje metodę what():
#include <exception>
#include <string>
class DatabaseException : public std::exception {
std::string message;
public:
DatabaseException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
};
class ConnectionException : public DatabaseException {
public:
ConnectionException(const std::string& host)
: DatabaseException("Failed to connect to: " + host) {}
};
class QueryException : public DatabaseException {
public:
QueryException(const std::string& query)
: DatabaseException("Invalid query: " + query) {}
};Ta hierarchia pozwala blokom catch obsługiwać wyjątki na różnych poziomach szczegółowości:
try {
// operacje na bazie danych...
throw ConnectionException("localhost");
} catch (const ConnectionException& e) {
std::cout << "Connection issue: " << e.what() << "\n";
} catch (const DatabaseException& e) {
std::cout << "Database error: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << "\n";
}Specyfikator noexcept przy what() jest ważny — gwarantuje, że metoda nie zgłosi wyjątku, czego wymaga interfejs klasy bazowej. Uporządkuj bloki catch od najbardziej szczegółowego do najbardziej ogólnego, ponieważ C++ używa pierwszego pasującego programu obsługi.
Wyzwanie
ŁatwyZbudujmy system przetwarzania plików z własną hierarchią wyjątków, która obsługuje różne rodzaje błędów związanych z plikami. Utworzysz wyjątki specyficzne dla domeny, dziedziczące po bazowej klasie wyjątków, co umożliwi precyzyjną obsługę błędów na różnych poziomach szczegółowości.
Podzielisz kod na trzy pliki:
FileExceptions.h: Zdefiniuj własną hierarchię wyjątków dla operacji na plikach.Utwórz bazową klasę
FileException, która dziedziczy postd::exception. Powinna przechowywać komunikat o błędzie i nadpisywać metodęwhat()ze specyfikatoremnoexcept.Następnie utwórz dwie klasy wyjątków pochodnych:
FileNotFoundException: przyjmuje nazwę pliku i tworzy komunikat:File not found: [filename]PermissionDeniedException: przyjmuje nazwę pliku i tworzy komunikat:Permission denied: [filename]
Każda klasa pochodna powinna wywołać konstruktor klasy nadrzędnej z odpowiednim komunikatem.
FileProcessor.h: Zdefiniuj klasęFileProcessor, która symuluje operacje na plikach i zgłasza własne wyjątki.Klasa
FileProcessorpowinna mieć metodęopenFile(const std::string& filename), która symuluje otwieranie pliku:- Jeśli nazwa pliku zawiera
missing, zgłoś wyjątekFileNotFoundException - Jeśli nazwa pliku zawiera
locked, zgłoś wyjątekPermissionDeniedException - W przeciwnym razie zwróć ciąg znaków
Successfully opened: [filename]
- Jeśli nazwa pliku zawiera
main.cpp: Odczytaj jedną nazwę pliku z wejścia.Utwórz obiekt
FileProcessori spróbuj otworzyć plik. Użyj bloku try-catch z procedurami obsługi ułożonymi od najbardziej szczegółowej do najbardziej ogólnej:- Przechwyć
FileNotFoundExceptioni wypisz:File Error: [message] - Przechwyć
PermissionDeniedExceptioni wypisz:Access Error: [message] - Przechwyć
FileExceptioni wypisz:General File Error: [message]
Jeśli nie zostanie zgłoszony żaden wyjątek, wypisz komunikat o powodzeniu zwrócony przez
openFile().- Przechwyć
Na przykład dla wejścia report.txt:
Successfully opened: report.txtDla wejścia missing_data.csv:
File Error: File not found: missing_data.csvDla wejścia locked_config.ini:
Access Error: Permission denied: locked_config.iniTo wyzwanie pokazuje, jak własne hierarchie wyjątków pozwalają obsługiwać błędy na odpowiednim poziomie szczegółowości: przechwytując konkretne wyjątki, gdy potrzebujesz precyzyjnej obsługi, lub przechwytując wyjątek bazowy, gdy chcesz jednolicie obsłużyć wszystkie błędy dotyczące plików.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "FileExceptions.h"
#include "FileProcessor.h"
using namespace std;
int main() {
string filename;
cin >> filename;
FileProcessor processor;
// TODO: Użyj bloku try-catch do obsługi wyjątków
// Uporządkuj bloki obsługi od najbardziej szczegółowego do najbardziej ogólnego:
// 1. Przechwyć FileNotFoundException — wypisz: "File Error: [message]"
// 2. Przechwyć PermissionDeniedException — wypisz: "Access Error: [message]"
// 3. Przechwyć FileException — wypisz: "General File Error: [message]"
// Jeśli nie wystąpi wyjątek, wypisz komunikat o powodzeniu zwrócony przez openFile()
try {
// TODO: Wywołaj processor.openFile() i obsłuż wynik
}
// TODO: Dodaj bloki catch w kolejności od najbardziej szczegółowego do najbardziej ogólnego
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów11Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieMixiny z użyciem CRTPIdiom PimplWymazywanie typówKlasy enum i silne typowanieObsługa wyjątków w OOPWłasne hierarchie wyjątków3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online