Rzuć słowo kluczowe w podpis funkcji

199

Jaki jest techniczny powód, dla którego za throwsłowo kluczowe C ++ w sygnaturze funkcji uważa się za złą praktykę ?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}
Konstantin
źródło
Zobacz ostatnie powiązane pytanie: stackoverflow.com/questions/1037575/...
laalto
1
Czy noexceptcoś zmienia?
Aaron McDaid,
12
Nie ma nic złego w opiniach na temat programowania. Kryteria zamknięcia są złe, przynajmniej w przypadku tego pytania. To interesujące pytanie z interesującymi odpowiedziami.
Anders Lindén
Należy zauważyć, że specyfikacje wyjątków są przestarzałe od C ++ 11.
François Andrieux,

Odpowiedzi:

127

Nie, nie jest to uważane za dobrą praktykę. Wręcz przeciwnie, jest to ogólnie uważane za zły pomysł.

http://www.gotw.ca/publications/mill22.htm zawiera wiele bardziej szczegółowych informacji na temat przyczyny, ale problem polega częściowo na tym, że kompilator nie jest w stanie wymusić tego, więc należy to sprawdzić w czasie wykonywania, co zwykle jest niepożądany. W każdym razie nie jest dobrze obsługiwany. (MSVC ignoruje specyfikacje wyjątków, z wyjątkiem throw (), co interpretuje jako gwarancję, że żaden wyjątek nie zostanie zgłoszony.

jalf
źródło
25
Tak. Istnieją lepsze sposoby dodawania białych znaków do kodu niż throw (myEx).
Assaf Lavie
4
tak, ludzie, którzy dopiero odkrywają specyfikacje wyjątków, często zakładają, że działają jak w Javie, gdzie kompilator jest w stanie je wymusić. W C ++ tak się nie stanie, co czyni je mniej przydatnymi.
jalf
7
Ale co powiesz na cel dokumentacyjny? Ponadto dowiesz się, jakich wyjątków nigdy nie złapiesz, nawet jeśli spróbujesz.
Anders Lindén
1
@ AndersLindén jaki cel dokumentacyjny? Jeśli chcesz po prostu udokumentować zachowanie swojego kodu, po prostu umieść komentarz nad nim. Nie jestem pewien, co masz na myśli z drugą częścią. Wyjątki, których nigdy nie złapiesz, nawet jeśli spróbujesz?
jalf
4
Myślę, że kod ma dużą moc, jeśli chodzi o dokumentowanie twojego kodu. (komentarze mogą kłamać). „Dokumentacyjny” efekt, o którym mówię, polega na tym, że będziesz wiedział na pewno, jakie wyjątki możesz złapać, a innych nie.
Anders Lindén
57

Jalf już z nim powiązał, ale GOTW całkiem dobrze ujmuje, dlaczego specyfikacje wyjątków nie są tak przydatne, jak można się spodziewać:

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

Czy komentarze są poprawne? Nie do końca. Gunc()może rzeczywiście coś rzucić i Hunc()może rzucić coś innego niż A lub B! Kompilator gwarantuje, że pokona ich bezsensownie, jeśli to zrobi ... och, i przebije twój program także bez sensu, przez większość czasu.

Właśnie do tego sprowadza się, prawdopodobnie skończysz z wezwaniem, terminate()a twój program umrze szybką, ale bolesną śmiercią.

Wniosek GOTW jest następujący:

Oto więc najlepsza rada, której nauczyliśmy się dzisiaj jako społeczność:

  • Morał # 1: Nigdy nie pisz specyfikacji wyjątku.
  • Morał # 2: Z wyjątkiem być może pustego, ale gdybym był tobą, unikałbym nawet tego.
coś
źródło
1
Nie jestem pewien, dlaczego miałbym rzucać wyjątek i nie byłbym w stanie o tym wspomnieć. Nawet jeśli jest to generowane przez inną funkcję, wiem, jakie wyjątki mogłyby zostać zgłoszone. Jedyny powód, dla którego widzę to to, że jest nudny.
MasterMastic,
3
@Ken: Chodzi o to, że pisanie specyfikacji wyjątków ma głównie negatywne konsekwencje. Jedynym pozytywnym efektem jest to, że pokazuje programiście, jakie mogą wystąpić wyjątki, ale ponieważ nie jest sprawdzany przez kompilator w rozsądny sposób, jest podatny na błędy i dlatego nie jest wiele wart.
sth
1
No dobrze, dzięki za odpowiedź. Myślę, że po to jest dokumentacja.
MasterMastic,
2
Niepoprawne. Należy napisać specyfikację wyjątku, ale chodzi o to, aby przekazać, jakie błędy powinien wywołać osoba dzwoniąca.
HelloWorld,
1
Tak jak mówi @StudentT: obowiązkiem tej funkcji jest zagwarantowanie, że nie będziemy rzucać dalej żadnych innych wyjątków. Jeśli tak, program kończy się tak, jak powinien. Zadeklarowanie rzutu oznacza, że nie jestem odpowiedzialny za tę sytuację, a osoba dzwoniąca powinna mieć wystarczającą ilość informacji, aby to zrobić. Brak deklarowania wyjątków oznacza, że ​​mogą one wystąpić w dowolnym miejscu i mogą być obsługiwane w dowolnym miejscu. To z pewnością bałagan anty-OOP. Projekt nie uchwycił wyjątków w niewłaściwych miejscach. Radzę nie wyrzucać pustych, ponieważ wyjątki są wyjątkowe, a większość funkcji i tak powinna być pusta.
Jan Turoň
30

Aby dodać nieco więcej wartości do wszystkich pozostałych odpowiedzi na to pytanie, należy poświęcić kilka minut na pytanie: Jaki jest wynik następującego kodu?

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

Odpowiedź: Jak wspomniano tutaj , wywołania programu std::terminate()i dlatego żaden z programów obsługi wyjątków nie zostanie wywołany.

Szczegóły: Pierwsza my_unexpected()funkcja jest wywoływana, ale ponieważ nie generuje ponownie pasującego typu wyjątku dla throw_exception()prototypu funkcji, w końcu std::terminate()jest wywoływana. Tak więc pełne wyjście wygląda następująco:

użytkownik @ użytkownik: ~ / tmp $ g ++ -o wyjątkiem. test wyjątkiem. test.cpp
użytkownik @ użytkownik: ~ / tmp $ ./except.test
dobrze - to było nieoczekiwane
zakończenie wywołane po rzuceniu instancji „int”
przerwane (rdzeń porzucone)

nieznany z nazwiska
źródło
12

Jedynym praktycznym efektem specyfikatora rzutu jest to, że jeśli myExctwoja funkcja rzuci coś innego niżstd::unexpected zostanie wywołana (zamiast zwykłego nieobsługiwanego mechanizmu wyjątku).

Aby udokumentować rodzaj wyjątków, które funkcja może zgłaszać, zwykle robię to:

bool
some_func() /* throw (myExc) */ {
}
Paolo Tedesco
źródło
5
Warto również zauważyć, że wywołanie std :: nieoczekiwane () zwykle powoduje wywołanie std :: terminate () i nagły koniec programu.
sth
1
- i że MSVC przynajmniej nie wdraża tego zachowania, o ile mi wiadomo.
jalf
9

Kiedy do języka dodano specyfikacje rzutów, było to w najlepszych intencjach, ale praktyka zapewniła bardziej praktyczne podejście.

W C ++ moją ogólną zasadą jest używanie specyfikacji rzutowania tylko w celu wskazania, że ​​metoda nie może rzucać. To silna gwarancja. W przeciwnym razie załóż, że może rzucić wszystko.

Greg D.
źródło
9

Cóż, podczas przeglądania Google na temat tej specyfikacji rzutów, rzuciłem okiem na ten artykuł: - ( http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx )

Tu też odtwarzam jego część, aby można ją było wykorzystać w przyszłości, niezależnie od tego, czy powyższy link działa, czy nie.

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

Gdy kompilator to zobaczy, z atrybutem „throw ()” kompilator może całkowicie zoptymalizować zmienną „bar”, ponieważ wie, że nie ma możliwości zgłoszenia wyjątku z metody MethodThatCannotThrow (). Bez atrybutu throw () kompilator musi utworzyć zmienną „bar”, ponieważ jeśli MethodThatCannotThrow zgłosi wyjątek, procedura obsługi wyjątków może / będzie zależeć od wartości zmiennej bar.

Ponadto narzędzia do analizy kodu źródłowego, takie jak prefast, mogą (i będą) korzystać z adnotacji throw () w celu poprawy swoich możliwości wykrywania błędów - na przykład, jeśli masz try / catch, a wszystkie wywoływane funkcje są oznaczone jako throw (), nie potrzebujesz try / catch (tak, ma to problem, jeśli później wywołasz funkcję, która może wywołać).

Pratik Singhal
źródło
3

Specyfikacja kompilacji bez rzutu dla funkcji wstawionej, która zwraca tylko zmienną składową i nie mogłaby generować wyjątków, może być wykorzystywana przez niektóre kompilatory do robienia pesymizacji (złożone słowo przeciwne do optymalizacji), które mogą mieć szkodliwy wpływ na wydajność. Jest to opisane w literaturze Boost: specyfikacja wyjątków

W przypadku niektórych kompilatorów specyfikacja „bez rzucania” na funkcje nieliniowe może być korzystna, jeśli dokonane zostaną odpowiednie optymalizacje, a użycie tej funkcji wpływa na wydajność w sposób, który to uzasadnia.

Dla mnie brzmi to, czy użyć go, czy nie, to połączenie wykonane przez bardzo krytyczne oko w ramach optymalizacji wydajności, być może przy użyciu narzędzi do profilowania.

Cytat z powyższego linku dla tych, którym się spieszy (zawiera przykład złych niezamierzonych efektów określenia rzutu na funkcję wbudowaną z naiwnego kompilatora):

Uzasadnienie specyfikacji wyjątku

Specyfikacje wyjątków [ISO 15.4] są czasami kodowane, aby wskazać, jakie wyjątki mogą zostać zgłoszone lub ponieważ programista ma nadzieję, że poprawią one wydajność. Ale rozważ następujący element z inteligentnego wskaźnika:

T & operator * () const throw () {return * ptr; }

Ta funkcja nie wywołuje żadnych innych funkcji; manipuluje tylko podstawowymi typami danych, takimi jak wskaźniki. Dlatego nigdy nie można wywoływać zachowania środowiska wykonawczego specyfikacji wyjątku. Funkcja jest całkowicie dostępna dla kompilatora; w rzeczywistości jest zadeklarowany jako wbudowany Dlatego inteligentny kompilator może łatwo wywnioskować, że funkcje nie są w stanie zgłaszać wyjątków, i dokonać tych samych optymalizacji, które dokonałby na podstawie pustej specyfikacji wyjątków. „Głupi” kompilator może jednak powodować wszelkiego rodzaju pesymizacje.

Na przykład niektóre kompilatory wyłączają wstawianie, jeśli istnieje specyfikacja wyjątku. Niektóre kompilatory dodają bloki try / catch. Takie pesymalizacje mogą być katastrofą wydajności, która sprawia, że ​​kod nie nadaje się do użytku w praktycznych zastosowaniach.

Choć początkowo atrakcyjne, specyfikacja wyjątku ma zwykle konsekwencje, które należy bardzo dokładnie przemyśleć. Największym problemem związanym ze specyfikacjami wyjątków jest to, że programiści używają ich tak, jakby wywoływały efekt, który chciałby programiści, zamiast efektu, który faktycznie mają.

Funkcja nielinearna to miejsce, w którym specyfikacja wyjątku „nic nie rzuca” może mieć pewne zalety w przypadku niektórych kompilatorów.

Pablo Adames
źródło
1
„Największym problemem związanym ze specyfikacjami wyjątków jest to, że programiści używają ich tak, jakby wywoływały efekt, który chcieliby programiści, zamiast efektu, jaki faktycznie mają”. To jest pierwszy powód błędów w komunikacji z maszynami lub ludźmi: różnica między tym, co mówimy, a tym, co mamy na myśli.
Thagomizer