2013-01-08 10 views
15

Moje pytanie dotyczy implementacji szablonu operatora przypisania przypisania shared_ptr w GCC 4.7.2, co do którego podejrzewam, że zawiera błąd.Czy istnieje błąd w implementacji przez GCC 4.7.2 operatora przypisania shared_ptr (opartego na szablonach)?

PREMISE 1: C++ 11 STANDARD

Oto podpis szablonu operatora przypisania mówię:

template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept; 

od C++ 11 Standard (20.7.2.2 .3):

"Odpowiednikshared_ptr(r).swap(*this)."

Innymi słowy szablon operatora przydziału definiowany jest w kategoriach szablonu konstruktora. Podpis konstruktora szablonu jest następujący:

template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept; 

Od C++ 11 Standard (20.7.2.2.1):

„wymaga: [...] konstruktor nie powinien uczestniczyć w rozdzielczości przeciążenia, chyba że Y * jest niejawnie wymienialne na T *. "

PREMISE 2: GCC 4.7.2 za REALIZACJA:

Teraz realizacja szablonie konstruktora GCC 4.7.2 wydaje mi się poprawne (std::__shared_ptr to klasa bazowa std::shared_ptr):

template<typename _Tp1, typename = 
typename std::enable_if<std::is_convertible<_Tp1*, _Tp*>::value>::type> 
__shared_ptr(__shared_ptr<_Tp1, _Lp>&& __r) noexcept 
    : 
    _M_ptr(__r._M_ptr), 
    _M_refcount() 
{ 
    _M_refcount._M_swap(__r._M_refcount); 
    __r._M_ptr = 0; 
} 

Jednak implementacja szablonu operatora przypisania GCC 4.7.2 jest następująca:

template<typename _Tp1> 
__shared_ptr& operator=(const __shared_ptr<_Tp1, _Lp>& __r) noexcept 
{ 
    _M_ptr = __r._M_ptr; 
    _M_refcount = __r._M_refcount; // __shared_count::op= doesn't throw 
    return *this; 
} 

Co uderzyło mnie to, że ta operacja jest nie zdefiniowana pod względem szablonu konstruktora, ani swap(). W szczególności, zwykłe przypisanie nie daje takiego samego rezultatu, jak gdy typy _Tp1* i _Tp* są wyraźnie sprawdzane pod kątem wymienialności przez std::is_convertible (, które mogą być wyspecjalizowane).

PREMISE 3: VC10 REALIZACJA

zauważyłem, że VC10 ma bardziej Wykonanie zgodne w tym zakresie, które uważam za prawidłowe i zachowuje się zgodnie z oczekiwaniami w moich przypadków testowych (podczas gdy GCC nie robi):

template<class _Ty2> 
_Myt& operator=(const shared_ptr<_Ty2>& _Right) 
{ 
    // assign shared ownership of resource owned by _Right 
    shared_ptr(_Right).swap(*this); 
    return (*this); 
} 

PYTANIE:

Czy rzeczywiście to błąd w realizacji shared_ptr GCC 4.7.2 za ? Nie mogłem znaleźć żadnego zgłoszenia błędu dla tego problemu.

Post Scriptum:

Jeśli chcesz mnie zapytać, co moi przypadki testowe są dlaczego mnie obchodzi ten pozornie nieistotnych szczegółach i dlaczego wydaje się sugerować, że muszę specjalizują std::is_convertible, zrób to na czacie. Jest to długa historia i nie ma sposobu jej podsumowania bez bycia źle zrozumianą (ze wszystkimi jej nieprzyjemnymi konsekwencjami). Z góry dziękuję.

+3

Chyba masz tam jest niepoprawny szablon konstruktora, dałeś ten, który konwertuje z innego surowego wskaźnika, a nie ten, który konwertuje z innego 'shared_ptr' –

+1

Nawet jeśli możesz specjalizować' is_convertible', nie możesz tego robić, chyba że utrzymujesz zachowanie wymagane przez standard ... –

+0

@ JonathanWakely: masz rację, przepraszam. Zmienię moje pytanie: –

Odpowiedz

19

Co mnie uderza to, że operacja ta nie jest definiowane w szablonie konstruktora, ani swap().

To nie musi być, tylko musi zachowywać jakby został on określony w tych kategoriach.

W szczególności proste zadanie _M_ptr = __r._M_ptr nie daje taki sam wynik jak w przypadku typów _Tp1* i _Tp* są wyraźnie zaznaczone w wymienialności przez std::is_convertible (które mogą być specjalnie).

zgadzam: [meta.type.synop]/1Zachowanie program, który dodaje specjalizacji dla każdego z szablonów klas określonych w niniejszym podrozdziale jest nieokreślony, chyba że podano inaczej.

Więc nie można zmienić znaczenie is_convertible<Y*, T*> a jeśli Y* jest zamienny do T* następnie przypisanie będzie działać, a ponieważ oba zadania (z wskaźnikiem a obiektem RefCount) są noexcept końcowy wynik jest równoznaczne z wymiany . Jeśli wskaźniki nie są wymienialne, zadanie nie powiedzie się, ale tak samo będzie shared_ptr(r).swap(*this), więc nadal jest równoważne.

Jeśli się mylę, proszę zgłoś zgłoszenie błędu, a naprawię je, ale nie sądzę, że program zgodny może wykryć różnicę między implementacją libstdC++ a wymaganiami standardu. To powiedziawszy, nie miałbym żadnych zastrzeżeń do zmiany tego, aby był wdrażany pod kątem swap. Obecna implementacja przyszła prosto z shared_ptr w Boost 1.32, nie wiem, czy Boost nadal robi to w ten sam sposób, czy też używa teraz shared_ptr(r).swap(*this).

[Pełne ujawnienie, jestem libstdC++ opiekun i głównie odpowiedzialny za kod shared_ptr, która została pierwotnie uprzejmie ofiarowanych przez autorów boost::shared_ptr a następnie mutiliated przeze mnie od tamtego czasu.]

+0

Obecna wersja Boost (1.52.0) implementuje ją jako 'this_type (r) .swap (* this);'. Wiem, że ta implementacja istnieje od wersji 1.44.0. Nie wiem, kiedy to się zmieniło. –

6

Implementacja w GCC jest zgodna z wymaganiami normy. Kiedy norma określa, że ​​zachowanie jednej funkcji jest równoważne z innym zestawem funkcji, oznacza to, że efekt pierwszego jest równoważny efektowi tych ostatnich funkcji, jak zdefiniowano w standardzie (a nie zaimplementowanym).

Ten standard nie wymaga użycia std::is_convertible dla tego konstruktora. Wymaga SFINAE dla konstruktora, ale nie wymaga SFINAE dla operatora przypisania. Wymaganie, aby typy były wymienialne, umieszczane jest w programie, a nie na implementacji std::shared_ptr i to twoja odpowiedzialność. Jeśli przekazywane typy nie są wymienialne, oznacza to błąd w twoim programie. Jeśli tak jest, implementacja musi zaakceptować kod, nawet jeśli chcesz wyłączyć użycie, specjalizując szablon is_convertible.

Potem znowu, specjalizujący is_convertible ograniczyć konwersje z wskaźników jest niezdefiniowane zachowanie, jak zmieniają semantykę szablonu bazowego, który jest wyraźnie dozwolone w normie.

To prowadzi do pierwotnego pytania, na które nie chcesz odpowiedzieć: jaki jest przypadek użycia, który skłonił Cię do przemyślenia tego rozwiązania. A może inaczej, dlaczego ludzie wciąż pytają o rozwiązania, a nie o rzeczywiste problemy, które chcą rozwiązać?

+3

** wymaga ** wymaga SFINAE dla tego konstruktora: [util.smartptr.shared.const]/17 _ Drugi konstruktor nie uczestniczy w rozdzielczości przeciążenia, chyba że 'Y * 'jest domyślnie zamienny na' T * '._ Ale nie wymaga SFINAE dla operatora przypisania, więc nie ma znaczenia. –

+0

@ JonathanWakely: Masz rację, spojrzałem na niewłaściwego konstruktora. –

Powiązane problemy