2009-07-15 5 views
43

Dlaczego niektórzy operatorzy mogą być przeciążeni tylko jako funkcje członkowskie, inni jako "wolne" funkcje, a reszta z nich jako jedno i drugie?Dlaczego niektórzy operatorzy mogą być przeciążeni tylko jako funkcje członkowskie, inni jako funkcje znajomych, a reszta z nich jako jedni i drudzy?

Jakie jest uzasadnienie tych?

Jak zapamiętać, którzy operatorzy mogą być przeciążeni jako co (członek, wolny lub oba)?

+5

@BROY Twoja edycja jest niepoprawna, funkcja _non-member_ niekoniecznie musi być _friend_. (A także odkryłem, że twoja edycja zmieniła się (bardzo często) (http://stackoverflow.com/posts/1132600/revisions) na oryginalne pytanie.) –

Odpowiedz

7

Uzasadnieniem jest to, że nie ma sensu, aby nie byli członkami, ponieważ rzecz po lewej stronie operatora musi być instancją klasy.

Na przykład, zakładając klasy A

A a1; 
.. 
a1 = 42; 

ostatniej instrukcji jest naprawdę wezwanie tak:

a1.operator=(42); 

To nie ma sensu na rzecz na LHS z . nie jest instancją A, więc funkcja musi być członkiem.

+2

Potrafię myśleć o zastosowaniach. Na przykład klasa B może teoretycznie chcieć zmienić sposób przypisania jej do A przez przeciążenie operatora = (A &, B), ale B może z jakiegoś powodu nie chcieć zdefiniować operatora rzutowania na A (na przykład, ponieważ nie chcesz inne niejawne rzuty do wystąpienia). To pragnienie może być nierozsądne, sprzeczne z powszechną praktyką, itp., Ale nie jestem pewien, czy to nie jest bezsensowne, czy też (jeszcze) złożyłeś skargę przeciwko niemu. –

+0

Cóż, to naprawdę nie ma znaczenia, czy nie przedstawiłem argumentów przeciwko - musimy zaakceptować to, co mówi standard. I oczywiście możesz zrobić (prawie) wszystko, co chcesz, za pomocą funkcji o nazwie przyjaciel. –

+1

Ma to sens, aby zabronić takich operacji na typach pierwotnych, ale dlaczego nie pozwolić na globalny * operator [] (const MyClass &, int) * i uczynić * operator [] (void *, int) * spowodować błąd z powodu prymitywów rodzaj? –

5

Ponieważ nie można modyfikować semantyki typów pierwotnych. Nie ma sensu określanie, w jaki sposób operator= działa na int, jak oceniać wskaźnik lub jak działa dostęp do tablicy.

0

Oto jeden z przykładów: Podczas przeciążenia << operator dla class T podpis będzie:

std::ostream operator<<(std::ostream& os, T& objT) 

gdzie wymaga realizacja będzie

{ 
//write objT to the os 
return os; 
} 

Dla operatora, pierwsza << Argument musi być obiektem ostream, a drugi argumentem obiektem klasy T.

Jeśli spróbujesz zdefiniować operator<< jako funkcję składową, nie będzie można jej zdefiniować jako std::ostream operator<<(std::ostream& os, T& objT). Jest tak dlatego, że funkcje binarnego operatora mogą przyjmować tylko jeden argument, a obiekt wywołujący jest niejawnie przekazywany jako pierwszy argument przy użyciu this.

Jeśli użyjesz podpisu std::ostream operator<<(std::ostream& os) jako funkcji składowej, w rzeczywistości otrzymasz funkcję składową std::ostream operator<<(this, std::ostream& os), która nie zrobi tego, co chcesz. Dlatego potrzebujesz operatora, który nie jest funkcją członka i może uzyskać dostęp do danych członków (jeśli twoja klasa T ma prywatne dane, które chcesz przesyłać strumieniowo, operator<< musi być przyjacielem klasy T).

28

Pytanie zawiera listę trzech klas operatorów. Umieszczając je obok siebie na liście pomaga, myślę, ze zrozumieniem, dlaczego kilka operatorzy są ograniczone w których mogą być przeciążone:

  1. Podmioty, które mają być przeciążone jako członków. Jest ich niewiele:

    1. Zadanie operator=(). Zezwalanie na przypisania nie będące członkami wydaje się otwierać drzwi dla operatorów przejmujących zadania, np., przez przeciążanie dla różnych wersji kwalifikacji const. Biorąc pod uwagę, że operatorzy przydziału są raczej podstawowi, co wydaje się niepożądane.
    2. Wywołanie funkcji operator()(). Zasady wywoływania funkcji i przeciążania są wystarczająco skomplikowane, jakie są. Wydaje się, że nie zaleca się komplikowania reguł przez dopuszczanie operatorów wywołań funkcji, które nie są członkami.
    3. Indeks dolny operator[](). Korzystanie z interesujących typów indeksów może zakłócać dostęp do operatorów. Chociaż istnieje niewielkie ryzyko porwania przeciążenia, nie wydaje się, aby był duży zysk, ale interesujący potencjał do napisania wysoce nieoczywistego kodu.
    4. Członek klasy uzyska dostęp pod numerem operator->(). Off-hand Nie widzę żadnego niewłaściwego nadużycia przeciążenia tego operatora, który nie jest członkiem. Z drugiej strony nie widzę żadnego. Ponadto operator dostępu do członów klasy ma raczej specjalne zasady, a granie z potencjalnymi przeciążeniami, które ingerują w nie, wydaje się niepotrzebną komplikacją.

    Mimo że możliwe jest przeciążenie, każdy z tych elementów nie jest członkiem (zwłaszcza operator indeksu, który działa na tablicach/wskaźnikach i może to być po obu stronach połączenia) wydaje się zaskakujący, jeśli np. , przydział może zostać przejęty przez przeciążenie niebędące członkiem, które jest lepsze niż jedno z przydziałów członków. Operatory te są również raczej asymetryczne: generalnie nie chciałbyś wspierać konwersji po obu stronach wyrażenia z udziałem tych operatorów.

    To powiedziane, na przykład, dla biblioteki ekspresji lambda byłoby dobrze, gdyby było możliwe przeciążenie wszystkich tych operatorów i nie sądzę, aby istniała nieodłączna przyczyna techniczna uniemożliwiająca przeciążenie tych operatorów.

  2. Operatory, które muszą być przeciążone jako funkcje niezrzeszone.

    1. Dosłowne operator"" name()

    Ten operator zdefiniowany przez użytkownika jest trochę dziwny-ball i, prawdopodobnie nie naprawdę bardzo operator. W każdym razie nie ma obiektu, który mógłby wywołać tego członka, dla którego można zdefiniować członków: lewy argument literałów definiowanych przez użytkownika jest zawsze wbudowany.

  3. Nie wspomniano w pytaniu ale istnieje również operator, który nie może być przeładowany w ogóle:

    1. Selektor członkiem .
    2. Operator dostępu do obiektów wskaźnik do członka .*
    3. operator zakres ::
    4. operator trójskładnikowy ?:

    Tych czterech operatorów uznano za zbyt podstawowych, aby w ogóle się z nimi rozprawić. Chociaż w pewnym momencie pojawiła się propozycja, aby umożliwić przeładowanie operator.(), nie ma silnego wsparcia (głównym zastosowaniem będzie inteligentne odniesienie). Chociaż z pewnością istnieją pewne konteksty, które można sobie wyobrazić, gdzie byłoby miło przeciążać tych operatorów.

  4. Operatory, które mogą być przeciążone jako członkowie lub jako członkowie niebędący członkami.To jest większość operatorów:

    1. Wstępnie i postinkrementacja/-decrement operator++(), operator--(), operator++(int), operator--(int)
    2. Sieć [jednoargumentowy] nieprawidłowego operator*()
    3. Sieć [jednoargumentowy] adresu z operator&()
    4. Sieć [jednoargumentowy] znaki operator+(), operator-()
    5. negacja logiczna operator!() (lub operator not())
    6. bitowego inwersji operator~() (lub operator compl())
    7. Porównania operator==(), operator!=(), operator<(), operator>(), operator<=() i operator>()
    8. Sieć [binarny] arytmetyczna operator+(), operator-(), operator*(), operator/(), operator%()
    9. Sieć [binarny ] bitwise operator&() (lub operator bitand()), operator|() (lub operator bit_or()), operator^() (lub operator xor())
    10. Przesunięcie bitowe operator<<() i operator>>()
    11. Logika operator||() (lub operator or()) i operator&&() (lub operator and())
    12. Działanie/przydzielenie [email protected]=() (na @ jest symbolem odpowiedni uruchamiający()
    13. Sekwencja operator,() (dla których przeciążenie faktycznie zabija właściwość sekwencji!)
    14. Wskaźnik wskaźnik dostępu do elementu operator->*()
    15. Pamięć zarządzanie operator new(), operator new[](), operator new[]() i operator delete[]()

    Operatorzy które mogą być przeciążone albo jako członkowie lub osoby niebędące członkami nie są niezbędne do podstawowej konserwacji obiektów jak inne podmioty gospodarcze. To nie znaczy, że nie są ważne. W rzeczywistości, ta lista zawiera kilka operatorów, gdzie jest to raczej wątpliwe, czy powinny one być przeciążania (np adresu z operator&() lub podmioty, które normalnie powodują sekwencjonowania, czyli operator,(), operator||() i operator&&().

Oczywiście, standard C++ nie uzasadnia, dlaczego rzeczy są wykonywane w taki sposób, w jaki są wykonywane (i nie istnieją również zapisy z wczesnych dni, kiedy te decyzje były podejmowane). Najlepsze uzasadnienie można prawdopodobnie znaleźć w " Projekt i ewolucja C++ "przez Bjarne Stroustrup Przypominam, że operatorzy byli tam dyskutowani, ale nie wydaje się, aby dostępna była wersja elektroniczna.Ogólnie rzecz biorąc, nie sądzę, że istnieją naprawdę poważne powody, dla których ograniczenia są inne niż potencjalne komplikacje, które w większości nie były warte wysiłku. Wątpię jednak, aby ograniczenia zostały zniesione, ponieważ interakcje z istniejącym oprogramowaniem mogą zmienić nieprzewidywalny sens jakiegoś programu.

Powiązane problemy