2013-03-04 13 views
33

W standardowej C++ projektu (N3485), stwierdza następujące:Dlaczego operator std :: unique_ptr * throw i operator-> nie rzuca?

20.7.1.2.4 unique_ptr obserwatorów [unique.ptr.single.observers]

typename add_lvalue_reference<T>::type operator*() const; 

1 Requires: get() != nullptr. 
2 Returns: *get(). 

pointer operator->() const noexcept; 

3 Requires: get() != nullptr. 
4 Returns: get(). 
5 Note: use typically requires that T be a complete type. 

Widać, że operator* (dereferencja) nie jest określona jako noexcept, prawdopodobnie dlatego, że może spowodować uszkodzenie, ale następnie operator-> dla tego samego obiektu jest określone jako noexcept. Wymagania dla obu są takie same, jednak istnieje różnica w specyfikacji wyjątków.

Zauważyłem, że mają różne typy zwrotu, jeden zwraca wskaźnik, a drugi referencję. Czy to powiedzenie, że operator-> w rzeczywistości niczego nie dereferencji?

Faktem jest, że za pomocą operator-> na wskaźnik dowolnego rodzaju, który jest NULL, będzie segfault (jest UB). Dlaczego więc jeden z nich jest określony jako noexcept, a drugi nie?

Jestem pewien, że coś przeoczyłem.

EDIT:

Patrząc na std::shared_ptr mamy to:

20.7.2.2.5 shared_ptr obserwatorzy [util.smartptr.shared.obs]

T& operator*() const noexcept; 

T* operator->() const noexcept; 

To nie to samo ? Czy to ma coś wspólnego z inną semantyką własności?

+4

"Czy to powiedzenie, że operator-> faktycznie niczego nie usuwa?" Tak, to jest to –

+0

Może to pozwolić implementacjom na wyrzucenie zerowego wskaźnika, jeśli tak zdecydują. – Dan

Odpowiedz

25

Segfault wykracza poza system wyjątków C++. Jeśli usuniesz wskaźnik zerowy, nie zostanie wygenerowany żaden wyjątek (a przynajmniej, jeśli zastosujesz się do klauzuli Require:, zobacz poniżej).

Dla operator-> jest zwykle implementowany jako prosty return m_ptr; (lub return get(); dla unique_ptr).Jak widać, operator sam nie może rzucić - po prostu zwraca wskaźnik. Bez dereferencji, bez niczego. Język ma pewne szczególne zasady dotyczące p->identifier:

§13.5.6 [over.ref] p1

wyrazem x->m jest interpretowany jako (x.operator->())->m dla obiektu klasy x typu T jeśli T::operator->() istnieje i jeśli operator jest wybrany jako najlepszy funkcją match przez mechanizm rozwiązywania przeciążenia (13.3).

Powyższe odnosi się do rekursywnie, a na końcu musi dać wskaźnik, do którego jest używany wbudowany operator->. Dzięki temu użytkownicy inteligentnych wskaźników i iteratorów po prostu robią smart->fun(), nie martwiąc się o nic.

Notatka dla części specyfikacji zgodnych z Require:: Oznaczają one warunki wstępne. Jeśli ich nie spotkasz, powołujesz się na UB.

Dlaczego zatem jeden z nich jest określony jako noexcept, a drugi nie?

Szczerze mówiąc, nie jestem pewien. Wydaje się, że wyłuskiwanie wskaźnika zawsze powinno odbywać się pod noexcept, jednak unique_ptr pozwala całkowicie zmienić jaki jest typ wskaźnika wewnętrznego (przez deletera). Teraz jako użytkownik możesz zdefiniować zupełnie inną semantykę dla operator* na swoim typie pointer. Może to wylicza rzeczy w locie? Wszystkie te fajne rzeczy, które mogą rzucić.


Patrząc na std :: shared_ptr mamy to:

Łatwo to wytłumaczyć - shared_ptr nie obsługuje powyższy dostosowanie do rodzaju wskaźnik, co oznacza, że ​​zbudowany -w semantyce zawsze stosuje się - i *p gdzie p jest T* po prostu nie rzuca.

+4

+1 dla możliwości niestandardowej semantyki' operator * '. – Angew

+0

Możesz zmienić wewnętrzny typ wskaźnika, ale nie sądzę, że to z powodu deletera. Wskaźnik to "' std :: remove_reference :: type :: point', jeśli ten typ istnieje, inaczej T * " –

+0

Prawdopodobnie masz rację co do możliwości przeciążenia operatora *. –

-2

odniesieniu do:

Czy mówiąc, że operatora> faktycznie nie dereference czymkolwiek?

Nie, średnia ocena -> dla typu przeciążenia operator-> jest:

a->b; // (a.operator->())->b 

Tj ocena jest zdefiniowany rekurencyjnie, gdy kod źródłowy zawiera ->, operator-> nanosi wydzielić inną ekspresję z krytym -> że sama może odnosić się do operator-> ...

Jeśli chodzi o ogólną pytanie, jeśli wskaźnik jest null, zachowanie jest niezdefiniowana, a brak noexcept umożliwia implementację do throw. Jeśli podpis to noexcept, wówczas implementacja nie może być throw (a throw byłaby to rozmowa z std::terminate).

+1

Więc jaki jest typ '(a.operator ->())'? –

+0

Posiadanie niezdefiniowanego zachowania pozwala już implementacji na rzucenie, nawet jeśli funkcją jest 'noexcept'. –

+0

@TonyTheLion: Cokolwiek określisz jako ... może dać wskaźnik lub obiekt, jeśli da obiekt, zostanie wywołany 'operator->', aby 'a-> b' został przetłumaczony na' ((a.operator ->()) -> operator ->()) b', chyba że druga ocena daje również brak wskaźnika, w którym to przypadku ... –

1

Szczerze mówiąc, to po prostu wygląda jak usterka. Koncepcyjnie, a-> b powinno zawsze być równoważne (* a) .b, a dotyczy to nawet gdy a jest inteligentnym wskaźnikiem. Ale jeśli * a nie jest żadnym wyjątkiem, to (* a) .b nie jest, a zatem a-> b nie powinno być.

+0

http://open-std.org/jtc1/sc22 /wg21/docs/lwg-active.html#2337 jest pokrewne. Prawdopodobnie zostanie zamknięty jako NAD, co nie spowoduje, że członkowie 'unique_ptr' będą konsekwentni. –

4

Cóż za wartość, oto mała część historii i to, jak rzeczy stały się teraz takie same.

Przed N3025, operator * nie został określony z noexcept, ale jego opis zawierał Throws: nothing. Wymóg ten usunięto N3025:

Zmiana [unique.ptr.single.observers] Jak wskazano (834) [Dla szczegółów patrz rozdział] Uwagi:

typename add_lvalue_reference<T>::type operator*() const;
1 - wymaga: get() !=nullptr.
2 - Zwroty: *get().
3 - Zgłasza: nic.

Oto treść „Uwagi” sekcja wspomniano wyżej:

W opinii tej pracy stała się kontrowersyjna, jak prawidłowo określić semantykę operacyjne operatora *, operator [], a heterogeniczne funkcje porównawcze. [structure.specifications]/3 nie określa jednoznacznie, czy element Returns (w przypadku braku nowego ekwiwalentu dla formuły) określa efekty. Dalej nie jest jasne, czy pozwoliłoby to na takie wyrażenie zwrotne, które wyjdzie przez wyjątek, jeśli dodatkowo zostanie dodany element Throws: -Nothing (czy wykonawca będzie musiał je złapać?). Aby rozwiązać ten konflikt, każdy istniejący element Throws został usunięty dla tych operacji, co jest co najmniej zgodne z [unique.ptr.special] i innymi częściami standardu. Wynikiem tego jest to, że dajemy teraz implicite wsparcie dla potencjalnie rzucających funkcji porównawczych, ale nie dla homogenicznych == i! =, Co może być nieco zaskakujące.

Ten sam papier zawiera również zalecenia dla edycji definicji operator ->, ale to brzmi następująco:

pointer operator->() const;
4 - Wymaga: get() != nullptr.
5 - Zwroty: get().
6 - Rzuty: nic.
7 - Uwaga: użycie zwykle wymaga, aby T było pełnym typem.

Jeśli chodzi o samo pytanie: sprowadza się do podstawowej różnicy między samym operatorem, a wyrażeniem, w którym operator jest używany.

Podczas korzystania z operator* operator usuwa zaznaczenie wskaźnika, który może zostać rzucony.

Podczas korzystania z operator-> sam operator po prostu zwraca wskaźnik (którego nie wolno rzutować). Wskaźnik ten jest następnie dereferencji w wyrażeniu zawierającym ->. Każdy wyjątek od dereferencji wskaźnika występuje w otaczającym wyrażeniu, a nie w samym operatorze.

Powiązane problemy