Konstruktor rzucający wyjątek C++

0

Witam,

Chciałem spytać bardziej doświadczonych kolegów po fachu, czy moje podejście do problemu jest... dopuszczalne ;).

Dysponuje klasą(dajmy na to KlasaA), której konstruktor przyjmuje kilka parametrów. Wartości tam przekazane są niezbędne do sensownego zainicjalizowania obiektu. Co jeżeli przekazane parametry są... błędne(pod względem logiki - np. przekraczają zakres problemu)?

Obecnie zaimplementowałem to poprzez rzucanie wyjątku w konstruktorze, jednak rozwiązanie to wydaje mi się średnio przyjemne. Obiekt klasy np. KlasaB zawierający składowe obiekty KlasyA musiałby aktywnie reagować na ten wyjątek(nie tylko wypisać komunikat błędu i zwinąć interes), a sterowanie pracą programu za pomocą wyjątków w C++ nie jest zbyt popularne.

Czy ktoś ma jakiś inny pomysł?

0

Jeśli dane pobierasz z wejścia to może po prostu analizuj poprawność danych przed przekazaniem ich do konstruktora?

0

Moim zdaniem nie ma w tym nic złego. Wyjątki wyrzuca się nie po to żeby sterować aplikacją, a po to aby obsłużyć błędy. Możesz np. sprawdzać dane wejściowe przed przekazaniem do konstruktora, ale zostawić tam wyrzucanie wyjątku gdybyś przy tworzeniu którejś instancji o tym zapomniał. Wyjątek Ci wtedy przypomni i od razu będzie wiadomo o co chodzi ;).

0
szasza napisał(a)

Jeśli dane pobierasz z wejścia to może po prostu analizuj poprawność danych przed przekazaniem ich do konstruktora?

Dane będę pochodziły z wejścia. Myślałem o tym ale na logikę żadna klasa której KlasaA jest elementem składowym nie powinna się tym interesować(no i kod musiałbym powtarzać w kilku/kilkunastu miejscach).

1

Jeżeli ustalenie, że dane wejściowe przekraczają możliwości obliczeniowe wymaga mnóstwo obliczeń (i np. tworzenia obiektów pomocniczych lub całej ich hierarchii), to możesz stworzyć pomocniczą metodę lub klasę, która będzie przeprowadzała obliczenia tylko w takim zakresie jaki potrzebny jest do stwierdzenia wadliwości (lub całkowitej poprawności) danych oraz przygotowania dotychczas stworzonego kodu aby otrzymał te wstępnie przeliczone dane bez niepotrzebnego powtarzania tych samych obliczeń. Wtedy przy poprawnych danych obiekt KlasaA będzie mógł zostać stworzony i od razu otrzyma wstępnie przeliczone dane, albo metoda tej nowej klasy stwierdzi wadliwość danych i jakiś prosty wynik mówiący o porażce (np. FALSE, 0 lub NULL).
Krótko mówiąc stwórz klasę C, która będzie miała zwykłą metodę zwracającą jakiś rezulat logiczny informujący o nie/prawidłowości danych. Metoda ta powinna przejąć część obliczeń z dotychczasowego konstruktora klasy A, a obliczone już dane umieści w swoich polach klasy C, następnie zrób dodatkowy konstruktor klasy A, który przyjmie jako argument zainicjowany obiekt klasy C z przygotowanymi już częściowo danymi (ale na pewno poprawnymi), dzięki czemu przeprowadzane obliczenia przyspieszą jego wykonanie, a na dodatek nie będzie już istniała potrzeba rzucania wyjątkiem z takiego konstruktora.
Sekwencja wywołań mogłaby wyglądać tak:

KlasaC daneDlaKlasyA(input); //cały obiekt mógłby powstać na (szybkim) stosie o ile danych nie będzie zbyt dużo
//...
if(daneDlaKlasyA.czyPoprawne())
{
//...
KlasaA blabla = new KlasaA(daneDlaKlasyA);
//...
}
else przerosłoMnie();
//...

Oczywiście metoda czyPoprawne() musiałaby przejąć część obliczeń znajdujących się w dotychczasowym konstruktorze KlasaA(input). A pozostałe obliczenia powinny znaleźć się w konstruktorze KlasaA(KlasaC dane), którzy nie wyrzuca wyjątków.

1

Ja trochę inaczej zawsze rozumiałem wyjątki. Kontrolowanie normalnej pracy programu za pomocą wyjątków jest złe z wielu powodów. Jeżeli np. wyrzucasz wyjątek związany z błędnymi parametrami wpisanymi od użytkownika, to łap go w funkcji która pobiera dane (i np. pozwól użytkownikowi wpisać dane jeszcze raz). Z kolei jak np. zawiódł operator new z powodu braku pamięci, to możesz nawet ten wyjątek zignorować (i pozwolić się programowi wykrzaczyć), ponieważ świadczy to o błędzie w programie. Ogólnie mówiąc łap wyjątki tam, gdzie jest to najsensowniejsze oraz stosuj je gdy zachodzi przewidywalny błąd (wspomniane złe wprowadzone dane, problemy z siecią, ogólnie tylko rzeczy poza właściwym programem). Przy debugowaniu możesz np. użyć asercji, dodatkowo stanowczo odradzam łapanie wyjątków metodą pokemonową(catch(...)).

0

Olamagato: Pomysł naprawdę interesujący. Tych obliczeń nie jest dużo, ale stworzenie dodatkowej klasy wydaje mi się całkiem sensowne. Prawdopodobnie póki co zastosuje zaproponowany przez Ciebie model, później może sprawdzanie powierzę metodzie statycznej(choć narzut związany z dodatkowymi obiektami pewnie będzie na tyle nieznaczący, że nic nie trzeba będzie zmieniać).

Zjarek napisał(a)

Ja trochę inaczej zawsze rozumiałem wyjątki. Kontrolowanie normalnej pracy programu za pomocą wyjątków jest złe z wielu powodów. Jeżeli np. wyrzucasz wyjątek związany z błędnymi parametrami wpisanymi od użytkownika, to łap go w funkcji która pobiera dane (i np. pozwól użytkownikowi wpisać dane jeszcze raz). Z kolei jak np. zawiódł operator new z powodu braku pamięci, to możesz nawet ten wyjątek zignorować (i pozwolić się programowi wykrzaczyć), ponieważ świadczy to o błędzie w programie. Ogólnie mówiąc łap wyjątki tam, gdzie jest to najsensowniejsze oraz stosuj je gdy zachodzi przewidywalny błąd (wspomniane złe wprowadzone dane, problemy z siecią, ogólnie tylko rzeczy poza właściwym programem). Przy debugowaniu możesz np. użyć asercji, dodatkowo stanowczo odradzam łapanie wyjątków metodą pokemonową(catch(...)).

Problem polega na tym, że dane pochodzą z kilku źródeł(plik, GUI, baza danych, sieć). O ile w przypadku GUI mogę polegać na ich poprawności(nie pozwolę użytkownikowi przeciągnąć elementu poza planszę), to w przypadku pliku/bazy danych/sieci byłby to zbytni optymizm. Jednocześnie błąd nie powinien wykrzaczać programu.
Z assertów korzystam od lat(odkąd zrozumiałem, że większości błędów nie przewidzę ;) ), Qt ma wygodne makra oferujące assert na sterydach(Q_ASSERT_X).

Dziękuję wszystkim, prawdopodobnie zastosuję się do pomysłu Olamagato.

1 użytkowników online, w tym zalogowanych: 0, gości: 1