2012-09-22 8 views
13

Zgodnie z cppreference.com, std::shared_ptr zapewnia pełny zestaw względnych operatorów (==,! =, <, ...), ale semantyki porównania nie są określone. Zakładam, że porównują one surowe wskaźniki do obiektów, do których się odwołują, i że std :: weak_ptr i std :: unique_ptr robią to samo.Czy można odziedziczyć po inteligentnych wskaźnikach C++ 11 i przesłonić operatorów względnych?

Dla niektórych celów wolałbym mieć operatorów względnych, którzy zamawiają inteligentne wskaźniki na podstawie porównywania przywoływanych obiektów (a nie wskaźników do nich). Jest to już coś, co robię bardzo często, ale z moimi własnymi "głupimi wskazówkami", które zachowują się jak surowe wskazówki, z wyjątkiem względnych operatorów. Chciałbym zrobić to samo ze standardowymi inteligentnymi wskaźnikami C++ 11. Więc ...

  1. Czy można dziedziczyć z C++ (11 inteligentnych wskaźników shared_ptr, weak_ptr i unique_ptr) i zastąpić operatorów względne?

  2. Czy są jakieś podstępne problemy, na które muszę zwrócić uwagę? Na przykład, czy istnieją inne metody potrzebne do wdrożenia lub użycia using dla zapewnienia, że ​​rzeczy działają poprawnie?

  3. Dla najlepszego lenistwa, czy jest dostępny szablon biblioteki, który zrobi to automatycznie?

Mam nadzieję, że to "oczywiście możesz to zrobić, idioto!" Coś w tym rodzaju, ale jestem trochę niepewny, ponieważ w standardowej bibliotece znajduje się kilka klas (takich jak choćby pojemniki takie jak std::map), z których nie powinno się dziedziczyć.

+3

Ogólnie rzecz biorąc, nie jest bezpiecznie dziedziczyć po wszystkim, co destruktor nie jest dynamiczny. To może być i jest często wykonywane, po prostu musisz być ostrożny. –

+0

@Mooing Duck - OK, późne wiązanie, które może być problemem dla destruktora i gdzie indziej - ma sens. Nie sądzę, że to jest dla mnie problem, przynajmniej w tej chwili, ale muszę to sprawdzić. Być może lepiej byłoby owinąć inteligentny wskaźnik jako element, a nie dziedziczyć. – Steve314

+1

Er, przez "dynamiczny", mam oczywiście na myśli "wirtualny" –

Odpowiedz

8

Ogólnie rzecz biorąc, nie jest bezpiecznie dziedziczyć z niczego, co destruktor nie jest dynamiczny. To może być i jest wykonywane powszechnie, po prostu musisz być naprawdę ostrożny. Zamiast dziedziczyć ze wskaźników, użyłbym tylko kompozycji, zwłaszcza, że ​​liczba członków jest względnie mała. Możesz być w stanie dokonać klasę szablonu dla tego

template<class pointer_type> 
class relative_ptr { 
public: 
    typedef typename std::pointer_traits<pointer_type>::pointer pointer; 
    typedef typename std::pointer_traits<pointer_type>::element_type element_type; 
    relative_ptr():ptr() {} 
    template<class U> 
    relative_ptr(U&& u):ptr(std::forward<U>(u)) {} 
    relative_ptr(relative_ptr<pointer>&& rhs):ptr(std::move(rhs.ptr)) {} 
    relative_ptr(const relative_ptr<pointer>& rhs):ptr(std::move(rhs.ptr)) {} 

    void swap (relative_ptr<pointer>& rhs) {ptr.swap(rhs.ptr);} 
    pointer release() {return ptr.release();} 
    void reset(pointer p = pointer()) {ptr.reset(p);} 
    pointer get() const {return ptr.get();} 
    element_type& operator*() const {return *ptr;} 
    const pointer_type& operator->() const {return ptr;} 

    friend bool operator< (const relative_ptr& khs, const relative_ptr& rhs) const 
    {return std::less<element>(*lhs,*rhs);} 
    friend bool operator<=(const relative_ptr& khs, const relative_ptr& rhs) const 
    {return std::less_equal<element>(*lhs,*rhs);} 
    friend bool operator> (const relative_ptr& khs, const relative_ptr& rhs) const 
    {return std::greater<element>(*lhs,*rhs);} 
    friend bool operator>=(const relative_ptr& khs, const relative_ptr& rhs) const 
    {return std::greater_equal<element>(*lhs,*rhs);} 
    friend bool operator==(const relative_ptr& khs, const relative_ptr& rhs) const 
    {return *lhs==*rhs;} 
    friend bool operator!=(const relative_ptr& khs, const relative_ptr& rhs) const 
    {return *lhs!=*rhs;} 
protected: 
    pointer_type ptr; 
}; 

Oczywiście prostota owijki ogranicza cię do najniższego wspólnego mianownika dla inteligentnych wskaźników, ale co tam. Nie są one dokładnie skomplikowane, można je utworzyć dla każdej z klas inteligentnego wskaźnika.

Podam ostrzeżenie, że nie podoba mi się sposób, w jaki działa ==, ponieważ może zwrócić wartość true dla dwóch wskaźników do różnych obiektów. Ale cokolwiek. Również nie przetestowałem kodu, może to się nie powieść dla niektórych zadań, takich jak próba skopiowania, gdy zawiera on unique_ptr.

+0

on „ponieważ może on powrócić prawda przez dwa wskaźniki do różnych obiektów.” -. Tak, ale to samo dotyczy normalnych przypadkach myśleć o tych pełnomocnikami że akurat być również inteligentne wskaźniki - jest to wskaźnik jest więcej szczegółów wdrażania niż dostarczonego abstrakcji – Steve314

+0

powinienem powiedzieć o tym wcześniej, ale jest także precedens w C++ dla wskaźników, które działają jak proxy i nie muszą być dereferencjonowane. - to '&' odniesienia ar Nazywane również wskaźnikami do samodzielnej dereferencji. – Steve314

+0

Nieco bardziej zawiłe niż to, co jest rzeczywiście potrzebne w tym przypadku użycia ... :) –

3

Niebezpieczne jest dziedziczenie z dowolnej klasy obsługującej przypisanie i tworzenie kopii, ze względu na ryzyko obcięcia instancji klasy pochodnej o połowę przez przypadkowe przypisanie jej do zmiennej bazowej. Wpływa to na większość klas i jest prawie niemożliwe do uniknięcia, dlatego wymaga czujności ze strony użytkowników klasy, ilekroć kopiują instancje.

Z tego powodu klasy przeznaczone do funkcjonowania jako bazy zwykle nie powinny obsługiwać kopiowania. Gdy konieczne jest kopiowanie, powinny one zamiast tego zapewnić coś w rodzaju: Derived* clone() const override.

Problem, który próbujesz rozwiązać, można najlepiej rozwiązać, pozostawiając rzeczy takimi, jakie są i dostarczając niestandardowe komparatory podczas pracy z takimi wskaźnikami.

std::vector<std::shared_ptr<int>> ii = …; 
std::sort(begin(ii), end(ii), 
      [](const std::shared_ptr<int>& a, const std::shared_ptr<int>& b) { 
       return *a < *b; 
      }); 
8

Pierwszą rzeczą, o której inni już wspomnieli, jest to, że dziedziczenie to nie jest droga. Ale raczej niż zawiłe owijki sugerowanej przez przyjętą odpowiedź, chciałbym zrobić coś znacznie prostsze: Wdrożenie własny komparator dla własnych typów:

namespace myns { 
struct mytype { 
    int value; 
}; 
bool operator<(mytype const& lhs, mytype const& rhs) { 
    return lhs.value < rhs.value; 
} 
bool operator<(std::shared_ptr<mytype> const & lhs, std::shared_ptr<mytype> const & rhs) 
{ 
    // Handle the possibility that the pointers might be NULL!!! 
    // ... then ... 
    return *lhs < *rhs; 
} 
} 

magia, która tak naprawdę nie jest magia w ogóle jest Argument Dependent Wyszukiwanie (inaczej: Koening Lookup lub ADL). Gdy kompilator napotka wywołanie funkcji, doda obszar nazw argumentów do wyszukiwania. Jeśli obiekty są instancjami szablonu, kompilator doda również przestrzenie nazw typów używanych do tworzenia instancji szablonu. Więc w:

int main() { 
    std::shared_ptr<myns::mytype> a, b; 
    if (a < b) {      // [1] 
     std::cout << "less\n"; 
    } else { 
     std::cout << "more\n"; 
    } 
} 

W [1], ponieważ a i b są obiektami typy zdefiniowane przez użytkownika(*) ADL będzie kopać i doda zarówno std i myns do zestawu odnośników. Będzie on następnie znaleźć standardową definicję operator< dla std::shared_ptr czyli:

template<class T, class U> 
bool std::operator<(shared_ptr<T> const& a, shared_ptr<U> const& b) noexcept; 

I będzie również dodać myns i dodać:

bool myns::operator<(mytype const& lhs, mytype const& rhs); 

Następnie, po wykończenia odnośników, rozdzielczość przeciążenie w rzutach, a jej ustali, że myns::operator< jest lepiej pasować do połączenia niż std::operator<, ponieważ jest idealnie dopasowane, w takim przypadku preferowane są szablony inne niż szablony. Będzie wówczas wywoływać własny numer operator< zamiast standardowego.

To staje się nieco bardziej skomplikowane, jeśli twój typ jest rzeczywiście szablonem, jeśli tak jest, skasuj komentarz, a ja przedłużę odpowiedź.


(*) To nieznaczne uproszczeniem. Ponieważ operator< może zostać zaimplementowany zarówno jako funkcja składowa, jak i funkcja bezpłatna, kompilator sprawdzi wewnątrz std::shared_ptr<> dla członka operator< (nieobecnego w standardzie) i znajomych. Zajrzy również do funkcji mytype dla friend ... i tak dalej. Ale na końcu znajdzie właściwą.

+0

to zrobić, gdy właściwe, ale, jak już wskazano w różnych komentarzach, że jest więcej rzeczy przejść wokół i prawdopodobnie się mieszać. Gdy wiesz, że używanie obu tych rzeczy jest właściwe, łączenie ich razem zapewnia ich wspólną podróż, a statyczne pisanie zapewnia dodatkowe kontrole. I chociaż nazwa "wskaźnik" może wprowadzać w błąd - jest to raczej rodzaj proxy, niezależnie od braku problemów sieciowych - nie oznacza to, że pakowanie funkcjonalności w ten sposób jest złe. – Steve314

+0

@ Steve314 Naprawdę nie rozumiem, co masz na myśli w swoim komentarzu. Darmowe funkcje w przestrzeni nazw typu są częścią tego samego * interfejsu * (w C++ interfejs obiektu nie jest ograniczony do jego członków, z powodu ADL), a to rozwiązanie faktycznie go wykorzystuje. Musisz tylko zdefiniować w tej samej przestrzeni nazw co twój typ (a więc w tym samym * interfejsie *) twoją darmową funkcję 'operator <'. Wiele osób jest zdezorientowanych z OO, co oznacza, że ​​można używać tylko funkcji składowych, ale C++ jest bogatszym językiem niż ten, i jest to jeden z przypadków, w którym świeci, można rozszerzyć bibliotekę. –

+2

W tym rozwiązaniu występuje * nic * do * przekazywania *. Operatory są zdefiniowane * raz * w przestrzeni nazw twojego typu i są dostępne * wszędzie *, * bezwiednie *, bez dodatkowego kodu w miejscu połączenia. To nie może być prostsze niż to. –

Powiązane problemy