Przeciążenie operatora: funkcja składowa a funkcja niebędąca składową

121

Czytałem, że przeciążony operator zadeklarowany jako funkcja członkowska jest asymetryczny, ponieważ może mieć tylko jeden parametr, a drugim parametrem przekazywanym automatycznie jest thiswskaźnik. Nie ma więc standardu, aby je porównać. Z drugiej strony przeciążony operator zadeklarowany jako a friendjest symetryczny, ponieważ przekazujemy dwa argumenty tego samego typu, dzięki czemu można je porównać.

Moje pytanie jest takie, że jeśli nadal mogę porównać wartość l wskaźnika z referencją, dlaczego preferowani są przyjaciele? (użycie wersji asymetrycznej daje takie same wyniki, jak wersja symetryczna) Dlaczego algorytmy STL używają tylko wersji symetrycznych?

badmaash
źródło
11
Twoje pytanie dotyczy tylko operatorów binarnych. Nie wszystkie przeciążone operatory są ograniczone do jednego parametru. Operator () może przyjmować dowolną liczbę parametrów. Z drugiej strony operatory jednoargumentowe nie mogą mieć żadnych parametrów.
Charles Salvia,
4
To jeden z wielu tematów omówionych w C ++ FAQ: Przeciążanie operatorów
Ben Voigt,

Odpowiedzi:

148

Jeśli zdefiniujesz funkcję przeciążoną przez operatora jako funkcję składową, kompilator tłumaczy wyrażenia, takie jak s1 + s2na s1.operator+(s2). Oznacza to, że przeciążona przez operatora funkcja członkowska jest wywoływana na pierwszym operandzie. Tak działają funkcje członkowskie!

Ale co, jeśli pierwszy operand nie jest klasą? Istnieje poważny problem, jeśli chcemy przeciążyć operator, którego pierwszy operand nie jest typem klasy double. Więc nie możesz tak pisać 10.0 + s2. Możesz jednak napisać funkcję składową przeciążoną przez operatora dla wyrażeń, takich jak s1 + 10.0.

Aby rozwiązać ten problem z porządkowaniem , definiujemy funkcję przeciążoną przez operatora jako friendJEŚLI potrzebuje dostępu do privateczłonków. Zrób to friendTYLKO wtedy, gdy potrzebuje dostępu do prywatnych członków. W przeciwnym razie po prostu uczyń to nieprzyjazną funkcją niebędącą członkiem, aby poprawić hermetyzację!

class Sample
{
 public:
    Sample operator + (const Sample& op2); //works with s1 + s2
    Sample operator + (double op2); //works with s1 + 10.0

   //Make it `friend` only when it needs to access private members. 
   //Otherwise simply make it **non-friend non-member** function.
    friend Sample operator + (double op1, const Sample& op2); //works with 10.0 + s2
}

Przeczytaj to:
Niewielki problem z porządkiem w operandach. Jak funkcje niebędące składnikami
poprawiają enkapsulację

Nawaz
źródło
2
„Zrób to friendtylko wtedy, gdy potrzebuje dostępu do prywatnych członków… i kiedy nie masz / nie znudzisz się pisaniem akcesorów, prawda?”
badmaash,
4
@Abhi: Wybierz, co chcesz: Ulepszona enkapsulacja kontra leniwy nawyk pisania!
Nawaz,
6
@matthias, nie wszystkie operatory są przemienne. Prosty przykład to a/b.
edA-qa mort-ora-y
3
Powszechnym sposobem uniknięcia wymagań operatorów niebędących członkami friendjest ich implementacja w kategoriach operatorów przypisania operacji (które prawie na pewno będą członkami publicznymi). Na przykład możesz zdefiniować T T::operator+=(const T &rhs)jako członka, a następnie zdefiniować osobę niebędącą członkiem T operator(T lhs, const T &rhs)jako return lhs += rhs;. Funkcja niebędąca składową powinna być zdefiniowana w tej samej przestrzeni nazw, co klasa.
Adrian McCarthy
2
@ricky: Ale jeśli lewa strona jest kopią (jak jest w moim komentarzu), to fakt, że lewa zmiana nie ma znaczenia.
Adrian McCarthy
20

Niekoniecznie jest to rozróżnienie między friendprzeciążeniami operatorów i przeciążeniami operatorów funkcji składowych, ponieważ występuje między przeciążeniami operatorów globalnych a przeciążeniami operatorów funkcji składowych.

Jednym z powodów, dla których warto preferować przeciążenie operatora globalnego , jest zezwolenie na wyrażenia, w których typ klasy pojawia się po prawej stronie operatora binarnego. Na przykład:

Foo f = 100;
int x = 10;
cout << x + f;

Działa to tylko wtedy, gdy istnieje przeciążenie operatora globalnego dla

Operator Foo + (int x, const Foo & f);

Zauważ, że przeciążenie operatora globalnego niekoniecznie musi być friendfunkcją. Jest to konieczne tylko wtedy, gdy potrzebuje dostępu do prywatnych członków Foo, ale nie zawsze tak jest.

Niezależnie od tego, gdyby Footylko miał przeciążenie operatora funkcji składowej, na przykład:

class Foo
{
  ...
  Foo operator + (int x);
  ...
};

... wtedy moglibyśmy mieć tylko wyrażenia, w których Foowystąpienie pojawia się po lewej stronie operatora plus.

Charles Salvia
źródło
3
+1 za rozróżnienie między funkcjami składowymi a funkcjami niebędącymi składnikami, a nie funkcjami składowymi i zaprzyjaźnionymi. Myślę, że dzisiaj powiedzielibyśmy „zakres globalny lub obszar nazw”.
Adrian McCarthy