Wyobraź sobie że referencja jest zakamuflowanym wskaźnikiem który ma "normalne techniki przypisania" (proszę nie czepiaj się słowa normalne inaczej ... proste). To jest takie że nie trzeba stosować * (gwiazdki) lub & (ampersand'a). Dodatkowo referencja nie może wskazać na adres pusty (w przeciwieństwie do wskaźnika) (Niech nikt nie wyskakuje ze sztuczką że można... to tylko namiesza w głowie @rybie).
Jak już to przemyślisz i przyjmiesz (choćby teraz "na wiarę") to zauważysz że referencja wskazuje zawsze na jakiś obiekt i _nie_może_ wskazać na bzdurne adresy...
Jeśli chcesz przypisaniem wykonywać "pociągi" jakie opisałeś ( a = b = c = 1), to każdy z elementów powinien zwrócić referencję czyli w uproszczeniu (dużym) "bezpieczne wskazanie" na obiekt. Gdyby operator= zwracał kopię, to przypisanie było by wykonane do kopii a później... była by ona niszczona bo kompilator orientował by się że nie jest potrzebna. Tu tego nie chcemy.
Zwrócenie wartości w operatorze operator= , na końcu to najczęściej: return *this; czyli... "bezpieczne wskazanie na siebie". A dlaczego * przed this? Bo zwracana jest referencja a ona ma taką składnię która potrzebuje _obiektu_ a nie wskaźnika (this jest przecież wskaźnikiem a więc wymaga wyłuskania * ).
Kiedy jest zwracana kopia z operatorów? Ano np. wtedy gdy potrzebujemy innej wartości. Np. operator post-inkrementacji który ma sygnaturę: TypKlasy operator++(int) { ... }
Jak widać ma kopię bo należy w {...} _stworzyć_ nową instancję obiektu z zachowaną kopią oryginalnego (this), inkrementować zawartość oryginalnego this i zwrócić tworzoną w {...} kopię. To powinna być kopia bo jak w {...} zrobisz obiekt na stosie (bez new), to zakończenie funkcji operatora zniszczy obiekt. Zwrócenie wskaźnika lub referencji (która przecież jest "bezpiecznym wskaźnikiem") będzie niebezpieczne i _niestety_ często działa choć kompilator wyraźnie ostrzega że robisz "kuku" :-)
Zrobię może jakiś prosty licznik abyś zrozumiał. W ramach ćwiczenia, dodaj sobie operatory pre/post dekrementacji i operator wyprowadzenia na strumień :-)
#include <iostream>
// Prosty licznik który po przekroczeniu max, zapętla się od min..
class Counter {
public:
// Konstruktory: bezargumentowy, 1 argumentowy i 2 argumentowy
Counter(unsigned min_value = 0, unsigned max_value = 10)
: min_value{min_value}, max_value{max_value}, value{min_value} {}
// Konstruktor kopiujący
Counter(const Counter& src)
: min_value{src.min_value}, max_value{src.max_value}, value{src.value} {}
// Operator przypisania zwracający ref. na siebie samego...
Counter& operator=(const Counter& src) {
min_value = src.min_value;
max_value = src.max_value;
return *this;
}
// Operator preinkrementacji który "inkrementuje siebie i siebie zwraca"
Counter& operator++() {
++value;
if(value > max_value) {
value = min_value;
}
return *this;
}
// Operator postinkrementacji który "robi swoją kopię, inkrementuje siebie i zwraca kopię
// która zawiera stary licznik"
Counter operator++(int) {
// Tu wykonam kopię "siebie"
Counter prev_counter(*this);
++(*this); // Tu zadziała operator++()
// Równie dobrze można napisać:
// this->operator++();
// lub..
// ++value;
// Zwrot kopii.
return prev_counter;
}
// Nie będę mieszał, zrobię proste get()
unsigned get() const {
return value;
}
private:
unsigned min_value;
unsigned max_value;
unsigned value;
};
int main() {
using namespace std;
Counter c = Counter(5, 7);
// Testy... jawnie łamane DRY..
cout << c.get() << endl;
++c;
cout << c.get() << endl;
++c;
cout << c.get() << endl;
c++;
cout << c.get() << endl;
}
PS. To chyba o czymś świadczy że coraz więcej ludzi otacza kod log'iem a nie cpp :-/ ? Może by ruszyć d*psko i poprawić formater :-) ?