2010-02-04 6 views
5

Szukałem odpowiedzi na to pytanie, ale nie znalazłem.C++ Ramyfikacje ignorowania wyjątku od konstruktora

Kiedy obiekt rzuca wyjątek na końcu konstruktora, czy obiekt jest ważny, czy jest to jeden z "zależny od techniki budowy"?

przykład:

struct Fraction 
    { 
     int m_numerator; 
     int m_denominator; 
     Fraction (double value, 
       int denominator); 
    }; 
    Fraction::Fraction(double value, int denominator) 
    : m_numerator(0), m_denominator(denominator) 
    { 
     if (denominator == 0) 
     { 
/* E1 */  throw std::logic_error("Denominator is zero."); 
     } 
     m_numerator = static_cast<int>(value * static_cast<double>(denominator)); 
     double actual_value = 0.0; 
     actual_value = static_cast<double>(m_numerator)/static_cast<double>(m_denominator); 
     double error = fabs(actual_value - value); 
     if (error > 5.0E-5) 
     { 
/* E2 */ throw std::logic_error("Can't represent value in exact fraction with given denominator"); 
     } 
    } 

Program:

int main(void) 
{ 
    try 
    { 
     Fraction f1(3.14159264, 4); // Throws exception, E2 above. 
    } 
    catch (...) 
    { 
     cerr << "Fraction f1 not exactly representable as fraction with denom. of 4.\n"; 
    } 

    // At this point, can I still use f1, knowing that it is an approximate fraction? 

    return EXIT_SUCCESS; 
} 

W tym przykładzie, mogą być stosowane po F1 wyjątkiem złowione, wiedząc, że jest to wartość w przybliżeniu?

Elementy danych zostały skonstruowane i zainicjowane.

Nie widzę żadnej reguły języka C++, która jest naruszona przez powyższe.

Edytuj: Zmieniono wartość delta błędu z 5.0E05 na 5.0E-5.

+2

Czy naprawdę masz dostęp do f1 za zasięgiem? – nusi

+0

Tak więc wyzwaniem jest wtedy, jak użyć obiektu, który ma konstrukcję * nieudaną *. Być może jest to dobry temat dla SO wiki. –

+0

** Absolutnie niemożliwe. ** Kiedy zostanie zgłoszony wyjątek, będzie propagował na zewnątrz. Oznacza to, że opuszcza obecny zakres, sprawdza, czy można go złapać, a jeśli nie, powtarza. Kiedy konstruktor zgłasza wyjątek, * bardzo * pierwszy zakres, który zostaje, jest tym, za którego obiekt jest tworzony za każdym razem. Albo dlatego, że jest bezpośrednio w bloku try/catch, albo dlatego, że musi opuścić zakres, aby go znaleźć. – GManNickG

Odpowiedz

4

Odpowiedź Jonathana jest poprawna. Ponadto, mimo że ułamek może być w prawidłowym stanie, nie polecam używania wyjątków do sterowania przepływem, a zwłaszcza , zwłaszcza do komunikacji o stanie obiektu. Zamiast tego należy rozważyć dodanie pewnego rodzaju interfejsu is_exactly_representable do interfejsu API obiektu frakcyjnego, który zwraca wartość bool.

+0

Dobra sugestia na temat 'jest_powielalnie_reprezentowalna'. Obiekt jest ważny, ale nie w pełni dokładny. Podobny do tego, w jaki sposób 1/3 nie może być dokładnie odwzorowany w postaci zmiennoprzecinkowej. –

5

Po wykryciu wyjątku f1 jest poza zakresem. To już nie istnieje, więc nie możesz z niego korzystać.

+0

Czy mam rację, że gramatyka języka uniemożliwia użycie obiektu po wystąpieniu błędu konstruktora z powodu problemu z zasięgiem? Nie widzę sposobu wychwycenia wyjątku bez bloku 'try' /' catch' i konstrukcji w bloku 'try'. –

+0

@Thomas: Prawidłowo, nie możesz wychwycić wyjątku bez bloku catch. Gdybyś go nie miał, opuściłoby 'main', a system operacyjny by go złapał. (W ten sposób dajesz te fajne okna dialogowe awarii). – GManNickG

+0

@Thomas Matthews Język uniemożliwia użycie dowolnych zmiennych, które zostały zadeklarowane w ramach spróbuj poza próbą - ponieważ wtedy byłyby poza zakresem. Tak więc nie można nawet próbować użyć takiej zmiennej poza blokiem try. Jedynym sposobem obejścia tego problemu byłoby użycie wskaźników, a ponieważ obiekt jest nieprawidłowy, powinien zostać usunięty, a nie używany. –

1

Zgadzam się z fbrereto.

Jeśli rzucisz błąd w konstruktorze, który jest równoznaczny z powiedzeniem "konstruowanie tego obiektu nie działa" lub "obiekt nie może zostać utworzony" i jako taki musisz wtedy obsłużyć ten fakt - ja bym tylko zrób to dla fatalnych błędów, dla których obiekt nie może być użyty w inny sposób, na przykład nie może otworzyć pliku, który, jak oczekiwaliśmy, będzie mógł otworzyć w klasie MySettingsReader.

+0

+1 Chociaż nie jest to rozwiązanie, które wybieram, daję +1 w celu wyjaśnienia, kiedy należy korzystać z wyjątków. –

2

Nie, po przekroczeniu zakresu zdefiniowanego przez f1, nie można już używać obiektu. Tak więc, w kodzie:

try 
{ 
    Fraction f1(3.14159264, 4); // Throws exception, E2 above. 

    // f1 can be used until here 
} 
catch (...) 
{ 
} 

// The scope that f1 was defined in is over, so the compiler will not let 
// you reference f1 

Powiedział, że być może powinieneś przemyśleć rzuca wyjątek, gdy nie można reprezentować rzeczywistą wartość. Dlatego, że mogą być stosowane wyłącznie w odniesieniu do niektórych zastosowań, można wymagać rozmówcę poprosić go:

enum FractionOption { disallowInexact, allowInexact }; 

Fraction::Fraction(double value, int denominator, 
        FractionOption option = disallowInexact) 
{ 
    ... 
    if ((option == disallowInexact) && (error > 5.0E-5)) 
    { 
     throw std::logic_error("Can't represent value ..."); 
    } 
} 

Fraction f1(3.14159264, 4, allowInexact); 
+0

+1, Lubię dodawać zasady do konstruktora. To mi nie przyszło do głowy. {Chociaż zasady były używane tylko z szablonami, na przykład w * Modern C++ Design * autorstwa Andrei Alexandrescu.} –

0

Jeśli obiekt zgłasza wyjątek podczas budowy nie technicznie unieważnia obiektu. W twoim przykładzie f1 wykracza poza zakres i dlatego jest zwalniany, gdy zostanie zgłoszony wyjątek.

Jeśli f1 był wskaźnikiem, który został przydzielony i skonstruowany wewnątrz bloku try, a konstruktor (a nie alokator) zwrócił wyjątek, to wskaźnik byłby prawidłową, przydzieloną pamięcią. To, czy obiekt w tej pamięci zawierał ważne dane, zależy od twojego konstruktora; w zasadzie, jeśli dane były ważne przed rzutem, byłyby ważne po rzucie.

Wygląda na to, że to, co próbujesz zrobić, nie jest odpowiednim narzędziem do wyjątków, i chciałbym zakwestionować twój projekt tutaj.Zgłaszanie wyjątku w wywołaniu konstruktora zasadniczo oznacza, że ​​obiekt nie został poprawnie skonstruowany i nie powinien być używany.

+2

"Twój wskaźnik byłby prawidłową, przydzieloną pamięcią" - To jest nieprawidłowe. –

+0

Przydziały z grubsza przetłumaczyć z: 'foo * f = 0; f = new foo() 'to:' foo * f = 0; void * __memory = 0; foo * __dummy = 0; spróbuj {__memory = :: operator new (sizeof (f)); __dummy = new (__memory) foo(); } catch (...) {:: operator delete (__ memory); throw;} f = __dummy; 'I tak, jestem dobrze poinformowany, że wygląda brutto. : P Ale wskaźnik pozostaje niezmieniony, gdy 'nowy' rzuca; a pamięć jest wolna. – GManNickG

1

po JMD

Jest f1 dostępne w klauzuli catch. Odpowiedź brzmi również nie. W związku z tym zasady ustalania zakresu uniemożliwiają nawet zadawanie tego pytania w kodzie.

Jedyną rzeczą, która będzie oddać, że obiekt istnieje byłoby, gdyby jej zabrakło destructor - ale to nie działa, jeśli contructor nie zakończył

2

rzucać w konstruktorze = konstrukcja nie powiodło się -> przedmiot nieużyteczny

Jak już wspomniano, jeśli wyjątek zostanie zgłoszony, to obiekt wykracza poza zakres. Jednakże, może być zainteresowany w przypadku gdy przeznaczyć obiekt:

f = new Fraction(3.14159264, 4); 

W tym przypadku f jest bezużyteczny, ponieważ konstruktor nie zakończyć pracę, a wskaźnik nie są przydzielane. Niszczyciel nie zostanie wywołany, a pamięć jest zdekalokowana deallocated, dlatego nie ma możliwości użycia tego obiektu.

Dlatego skonstruuj obiekt normalnie, nie używaj wyjątków, jeśli zamierzasz korzystać z klasy. Użyj funkcji member is_exact(), aby zdecydować, czy jest dokładna po zbudowaniu.

+0

Destruktor nie jest wywoływany, jeśli obiekt jest tworzony na stosie. – villintehaspam

+0

@villintehaspam, prawda, skupiałem się na niezabezpieczonym wariancie. –

+0

Ważnym faktem jest, że jeśli konstruktor frakcji wyrzuca, to przypisanie do wskaźnika "f" nigdy nie jest realizowane, więc adres pamięci zwrócony przez 'new' jest w rzeczywistości niedostępny, niezależnie od tego, co dzieje się z przydzieloną pamięcią. – mloskot

1

Gdy przedmiot zgłasza wyjątek na końcu konstruktora jest obiektu ważne, czy jest to jeden z tych „zależy od zastosowanej techniki budowlanej”?

Tak, to zależy rzeczywiście. To zależy od tego, co masz na myśli: obiekt jest ważny. Poprawne może mieć wiele znaczeń.

Wiadomo, że obiekt, którego budowa została przerwana, jest częściowo skonstruowanym obiektem. Jeśli uznamy częściową konstrukcję za nieprawidłowy stan, to tak, taki obiekt będzie nieważny.

Zniszczenie gwarantuje jednak zgodnie z tym schematem podanym w C++/15,2:

Obiekt, który jest częściowo skonstruowany lub częściowo niszczone będą miały destruktory wykonywane dla wszystkich jego wykończona podobiektów, to znaczy dla podobiektów, dla których konstruktor wykonał wykonanie , a destruktor jeszcze nie rozpoczął wykonywania .

Oznacza to, że tylko podobiekty częściowo konstruowanej obiektu zostanie prawidłowo zniszczona ale destructor częściowo konstruowanej samego obiektu będzie nie być wykorzystane.

0

Jeśli w jakiejkolwiek fazie podczas konstruktora zostanie zgłoszony wyjątek (i nie zostanie przechwycony w konstruktorze), obiekt nie będzie istnieć.Wszystkie zmienne składowe, które zostały już pomyślnie zbudowane, zostaną zdekonstruowane w dokładnej kolejności odwrotnej do konstrukcji. Jeśli wyjątek został wyrzucony z konstruktora zmiennej członka lub w inny sposób na liście inicjalizacji, zmienna składowa, której nie udało się skonstruować, nie ma wywoływanego destruktora i żadne z nich nie ma po nim żadnego.

W każdym przypadku, zakładając, że używasz RAII wszędzie, wszystkie zasoby są poprawnie zwalniane i nie będzie żadnych obiektów, do których można uzyskać dostęp. W przypadku ptr = new Foo(); zmienna ptr zachowuje swoją dawną wartość. Podobnie smartptr.reset(new Foo()); w ogóle nie wywoła funkcji resetowania.

Zwróć uwagę na błąd użycia operatora new w wyrażeniach konstruujących inne obiekty: somefunc(Foo(), new Bar());. Jeśli konstruktor Foo ulegnie awarii, może wystąpić przeciek pamięci (w zależności od kolejności, w jakiej kompilator przetworzył argumenty).

Powiązane problemy