Zastanawiam się, w jakich przypadkach sensowne jest stosowanie semantyki ruchu podczas przeciążania operatora + i/lub operatora + =. Chociaż jest to wyjaśnione w this question, jak można to zrobić, nie mogę się skupić na tym, dlaczego to zrobiłem. Rozważmy operator + =. Jeśli po prostu przekazuję prawą stronę przez odniesienie i dokonuję odpowiednich zmian na obiekcie po lewej stronie, i tak nie ma niepotrzebnych kopii. Więc wracamy do tego samego punktu: czy poruszenie semantyką byłoby w takim przypadku korzystne?Czy ma sens stosowanie semantyki przeniesienia dla operatora + i/lub operatora + =?
Odpowiedz
Tak i nie.
operator semantyka + =
ruch nie zawsze są pomocne dla operator+=
w ogóle, bo jesteś już modyfikując argument po lewej stronie (this
), więc masz już środków do pracy z najbardziej czasów.
Mimo to optymalizacja może być tego warta. Wyobraź sobie implementację std::string
, której domyślny konstruktor nie alokuje żadnej pamięci. Wtedy std::string::operator+=(std::string&&)
może po prostu ukraść zasoby z RHS. Albo wyobraź sobie, że bufor RHS jest wystarczająco duży, aby pomieścić wszystko, ale LHS nie jest, wtedy jeśli możesz użyć bufora RHS, to jesteś złoty: po prostu zamień i wstań.
Może warto, ale trzeba się uczyć. Dlatego:
T& T::operator+=(T const&)
: zawsze obecnyT& T::operator+=(T&&)
: umożliwienie semantykę przesunięta, gdy ma to sens
operator +
Tutaj zawsze jest przydatna (pod warunkiem, że mówimy o klasach dla których przydatne są semantyki ruchu).
Rzecz polega na tym, że operator+
produkuje tymczasowo (na niebiesko), więc generalnie musi utworzyć zasoby dla tego tymczasowego. Jeśli jednak uda im się je ukraść, a nie tworzyć, jest to z pewnością tańsze.
Jednak nie trzeba dostarczyć wszystkie przeciążeń:
T operator+(T const&, T const&)
T operator+(T&&, T const&)
T operator+(T const&, T&&)
T operator+(T&&, T&&)
(wymagana ujednoznacznienie)
Nie można ponownie wykorzystać ten sam podstęp, który operator=
użyj i twórz tymczasowe prawo w podpisie funkcji (przez wzięcie jednego argumentu za pomocą kopii). Jeśli typ jest ruchomy, konstruktor ruchu zostanie wywołany, w przeciwnym razie będzie to konstruktor kopiowania, ale ponieważ i tak potrzebujesz tymczasowego, bez utraty wydajności.
inline T operator+(T left, T const& right) { left += right; return left; }
inline T operator+(T const& left, T right) { right += left; return right; } // commutative
inline T operator+(T left, T&& right) { left += right; return left; } // disambiguation
Nie wiele zyskać (3 zamiast 4), ale cóż, wezmę co mogę!
Oczywiście dla napisu, operator+
nie jest przemienny (dlatego jest to złe przeciążenie), więc rzeczywista implementacja drugiego przeciążenia wymagałaby metody prepend
.
EDIT: po Move semantics and operator overloading wydaje się, że byłem nieco zbyt entuzjastyczny. Kradzież z odpowiedzią na Ben Voigt, otrzymujemy:
inline T operator+(T left, T const& right) { left += right; return left; }
inline T operator+(const T& left, T&& right) { right += left; return right; }
Z drugiej strony, wydaje się to tylko pracować dla operacji przemiennych; -
nie działa w ten sposób, ale prawdopodobnie można go zaadaptować, /
i %
z drugiej strony ...
Lepiej rób 'right + = left; return right; 'sprawić, że po powrocie rozważy" right "jako wartość r. –
@ JohannesSchaub-litb: Czy mogę przypuszczać, że mówisz o ostatnim przypadku? Zastanawiałem się też nad tym i czy warto przeciążać 'T && T :: operator +() i &'. –
Mówię o wszystkich trzech przypadkach. Jeśli 'T' ma konstruktor ruchu, możesz go użyć, zwracając odpowiednio" left "lub" right ". Tak jak jest teraz z twoim kodem, jeśli 'operator + =' zwraca 'T &', zawsze skopiujesz parametr do wartości zwracanej zamiast go przenosić. –
Jeśli dodajesz dwa ciągi, wektory itd., Których nie możesz "przenieść", nie ma to sensu. Ale jeśli dodajesz, mówisz o połączonych listach, gdzie dołączanie listy jest prawdopodobnie operatorem O (1), jeśli chcesz poświęcić prawą stronę, to ma to sens.
Generalnie zły pomysł na przeciążenie operatorów. Ponieważ: ** 1) ** jeśli oczywiście jest to dobry przypadek, prawdopodobnie jest to biblioteka już dla ciebie. ** 2) ** jeśli nie jest to oczywiście dobry przypadek, to prawdopodobnie bardzo źle to zrobić. – enobayram
@enobayram sprawiają, że jest to funkcja "dopisz", jeśli jesteś chybiony wokół przeciążonych operatorów. Teraz wciąż możesz odpowiedzieć na pytanie. –
@obobayram, to nie ma żadnego sensu. Zgodnie z tą logiką, generalnie złym pomysłem jest pisanie kodu kiedykolwiek. – TonyK