Wyjątki – dlaczego program nie wykonuje kodu w catch?

0

Mam duże doświadczenie w C# i .NET i tam nigdy nie było problemu takiego, jak w C++ się zdarza.

Piszę aplikację wykorzystując Qt, na przykład robię operacje na tablicach na stercie (na przykład int * tab) i program się wysypuje przy próbie zapisu do elementu o dalszym numerze niż długość tablicy, co jest oczywiste i to się dzieje w obu językach (w C++ nie w każdym przypadku).

Próbując poszukać przyczyny problemu nieraz robiłem taką konstrukcję:

try
{
    // tablica ma 100 elementów
    tab[123] = 123;
}
catch (...)
{
    cout << "Jest problem, ale nie wiadomo jaki" << endl;
}

Po uruchomieniu kodu, wbrew pozorom program też się wywala i nie wykonuje kodu zawartego w catch. Dlaczego tak się dzieje?

Ten problem najczęściej się zdarza, jeżeli piszę program generujący i przetwarzający obraz poprzez pracę na strukturze bitowej, bądź też przy próbie wyświetlenia/odświeżenia obrazu na oknie. Przyczyną jest oczywiście jakiś błąd, najczęściej zapis bądź odczyt elementu dalszego niż długość tablicy lub elementu ujemnego.

Gdzieś czytałem, że żeby przechwycić każdy możliwy wyjątek, to w 'catch' podaje się '...' tak, jak w tym przykładzie. W jaki sposób można ustalić typ danego wyjątku (bez wymyślania wszystkich możliwych typów, które mogą zaistnieć w danej sytuacji)? Chodzi o to, żeby przy następnym teście tego samego kodu, w catch obsłużyć bardziej dokładnie wyjątek danego typu, co znacznie ułatwi ustalenie przyczyny problemu (na przykład wypisanie komunikatu zwracanego przez wyjątek), przy założeniu, że w danej sytuacji try/catch nie powinien mieć miejsca i nie powinien zdarzać się wyjątek.

1

Najprawdopodobniej wyjście poza granicę tablicy nie generuje wyjątku w C++. Jest to naleciałość z ze starego C, gdzie wyjątków zwyczajnie nie było, zapewne w celu uzyskania kompatybilności wstecznej. Nie wiem jak to jest np. z wektorami, bo od C++ dość mocno odszedłem ale to nie działa tak pięknie jak w C# i trzeba się z tym pogodzić.

Taki wyjątek musiałbyś sobie zwyczajnie samemu zdefiniować i rzucić. Takie zachowanie jak w Twoim kodzie to UB.
Swoją drogą dzisiaj raczej nie korzysta się z dynamicznej alokacji pamięci na rzecz wbudowanych kontenerów.

6
// tablica ma 100 elementów
    tab[123] = 123;

to jest UB i po tym kodzie juz nie masz zadnej pewnosci (w teorii) co sie bedzie dziac w aplikacji bo zmieniasz dane w pamieci ktorej nie posiadasz! (np mozesz nadpisac stan obiektu czy sam obiekt)
To, ze sie wywala jest calkiem normalne, nie mozna takich rzeczy robic w c++ bo catch nam wszystko zlapie ;)

no i wyjscie poza tablice nie generuje wyjatku ;)

3

Od siebie dodam, że w przypadku zwykłej tablicy, lub tablicy dynamicznej (a także w przypadku niektórych kontenerów) zapis tab[2] jest tym samym co *(tab + 2) a więc zwyczajnie inkrementując wskaźnik nie masz żadnej kontroli nad tym, gdzie trafisz.

Jeśli chcesz mieć nad tym kontrolę to polecam użycie kontenerów, tak jak pisał @grzesiek51114. Przy czym musisz pamiętać, że (na przykładzie vectora) operator[] też nie rzuca wyjątku, więc czasem trzeba uciec się na przyklad do std::at, który już rzuca wyjątek:
std::out_of_range.

Dokumentacja prawdę Ci powie.


edit: Odpowiadając jeszcze na pytanie:

W jaki sposób można ustalić typ danego wyjątku (bez wymyślania wszystkich możliwych typów, które mogą zaistnieć w danej sytuacji)?

Możesz spróbować użyć typu: std::exception, po którym dziedziczą pewne wyjątki. Możesz je złapać właśnie ich typem bazowym. Będziesz mógł wtedy wprost wyświetlić komunikat.
Przykład:

#include <iostream>
#include <vector>

int main( void )
{
    std::vector<int> v(10);

    for( auto it : v )
    {
        std::cout << it << std::endl;
    }

    try
    {
        std::cout << "11: " << v.at( 11 ) << std::endl;
    }
    catch( std::exception& e )
    {
        std::cout << e.what() << std::endl;
    }

    std::cin.get();
    return 0;
}

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