Dlaczego Operator copy assignment musi zwracać referencję / const?

W C++ pojęcie zwracania referencji z operatora copy assignment jest dla mnie niejasne. Dlaczego operator przypisania kopii nie może zwrócić kopii nowego obiektu? Dodatkowo, jeśli mam klasę A i następujące:

A a1(param);
A a2 = a1;
A a3;

a3 = a2; //<--- this is the problematic line

operator= definiuje się następująco:

A A::operator=(const A& a)
{
    if (this == &a)
    {
        return *this;
    }
    param = a.param;
    return *this;
}
Author: Azeem, 2010-06-24

6 answers

Ściśle mówiąc, wynik operatora przypisania kopii nie musi zwracać referencji, chociaż aby naśladować domyślne zachowanie kompilatora C++, powinien zwrócić Nie-const odniesienie do obiektu, do którego jest przypisany (domyślnie wygenerowany operator przypisania kopii zwróci nie-const odniesienie-C++03: 12.8/10). Widziałem sporo kodu, który zwraca void z przeciążenia kopiowania i nie pamiętam, kiedy to spowodowało poważny problem. Return void will uniemożliwia użytkownikom 'przypisanie łańcucha' (a = b = c;) i uniemożliwi użycie wyniku przypisania w testowym wyrażeniu, na przykład. Chociaż ten rodzaj kodu nie jest bynajmniej niespotykany, nie sądzę, aby był on szczególnie powszechny-szczególnie w przypadku typów nie-prymitywnych (chyba że interfejs klasy przeznacza się dla tego rodzaju testów, takich jak iostreams).

Nie polecam ci tego, tylko zaznaczam, że jest to dozwolone i że nie wydaje się, aby powodowało to wiele problemy.

Te inne pytania są powiązane (prawdopodobnie nie do końca oszukańcze), które mają informacje/opinie, które mogą Cię zainteresować.

 57
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:32:26

Trochę wyjaśnienia, dlaczego lepiej jest zwracać przez odniesienie dla operator= zamiast zwracać przez wartość - - - ponieważ łańcuch a = b = c będzie działał dobrze, jeśli zwracana jest wartość.

Jeśli zwrócisz referencję, minimalna praca zostanie wykonana. Wartości z jednego obiektu są kopiowane do innego obiektu.

Jednakże, jeśli zwrócisz wartość operator=, wywołasz konstruktor i destruktor za każdym razem, gdy zostanie wywołany operator przypisania!!

Więc, podane:

A& operator=(const A& rhs) { /* ... */ };

Następnie,

a = b = c; // calls assignment operator above twice. Nice and simple.

Ale,

A operator=(const A& rhs) { /* ... */ };

a = b = c; // calls assignment operator twice, calls copy constructor twice, calls destructor type to delete the temporary values! Very wasteful and nothing gained!

Podsumowując, nie ma nic zyskanego zwracając przez wartość, ale wiele do stracenia.

(Uwaga: nie ma to na celu rozwiązania zalet posiadania operatora przypisania zwracającego lvalue. Przeczytaj inne posty, dlaczego tak może być lepiej)

 47
Author: Alex Collins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-20 10:02:01

Kiedy przeciążasz operator=, możesz napisać, aby zwrócić dowolny typ. Jeśli chcesz, możesz przeciążyć X::operator=, aby zwrócić (na przykład) instancję jakiejś zupełnie innej klasy Y lub Z. Jest to generalnie wysoce niewskazane.

W szczególności, Zwykle chcesz wspierać łańcuchowanie operator=, tak jak robi to C. Na przykład:

int x, y, z;

x = y = z = 0;

W tym przypadku, zwykle chcesz zwrócić lvalue lub rvalue typu przypisany do. Pozostawia to tylko pytanie, czy zwracać odniesienie do X, odniesienie const do X, czy X (przez wartość).

Zwracanie const odniesienia do X jest ogólnie słabym pomysłem. W szczególności, odniesienie const może wiązać się z obiektem tymczasowym. Czas życia czasowego jest przedłużony do czasu życia odniesienia, do którego jest związany-ale nie rekurencyjnie do czasu życia czegokolwiek, do czego może być przypisany. Ułatwia to powrót zwisającego odniesienie--odniesienie const wiąże się z obiektem tymczasowym. Czas życia tego obiektu jest przedłużany do czasu życia referencji (który kończy się na końcu funkcji). Do czasu, gdy funkcja powróci, czas życia referencji i tymczasowego dobiegnie końca, więc to, co jest przypisane, jest zwisającym odniesieniem.

Oczywiście zwrócenie odniesienia non-const nie zapewnia pełnej ochrony przed tym, ale przynajmniej sprawia, że pracujesz nad tym trochę ciężej. Można jeszcze (na przykład) zdefiniować niektóre lokalne, i zwracają odniesienie do niego (ale większość kompilatorów może i ostrzega o tym zbyt).

Zwracanie wartości zamiast odniesienia ma zarówno teoretyczne, jak i praktyczne problemy. Od strony teoretycznej, masz podstawowy rozłącznik między = zwykle oznacza i co oznacza w tym przypadku. W szczególności, gdzie przypisanie zwykle oznacza "weź to istniejące źródło i przypisz jego wartość do tego istniejącego miejsca docelowego", zaczyna to oznaczać coś więcej jak " weź to istniejące źródło, utwórz jego kopię i przypisz tę wartość do tego istniejącego miejsca docelowego."

Z praktycznego punktu widzenia, zwłaszcza przed wynalezieniem referencji rvalue, które mogły mieć znaczący wpływ na wydajność-tworzenie całego nowego obiektu w trakcie kopiowania A do B było nieoczekiwane i często dość powolne. Jeśli, na przykład, miałem mały wektor i przypisałem go do większego wektora, spodziewałbym się, że zajmie to, co najwyżej, czas, aby skopiować elementy małego wektora plus (trochę) stałe overhead, aby dostosować rozmiar wektora docelowego. Gdyby zamiast tego chodziło o dwie kopie, jedną ze źródła do temp, drugą z temp do miejsca docelowego i (co gorsza) dynamiczną alokację wektora czasowego, moje oczekiwania co do złożoności operacji byłyby całkowicie zniszczone. Dla małego wektora czas dynamicznej alokacji może być wielokrotnie wyższy niż czas kopiowania elementów.

Jedyna inna opcja (dodana w C++11) byłoby zwrócenie referencji rvalue. Może to łatwo doprowadzić do nieoczekiwanych rezultatów-przykute zadanie, takie jak a=b=c;, może zniszczyć zawartość b i / lub c, co byłoby dość nieoczekiwane.

Pozostawia to zwrócenie normalnego odniesienia (nie odniesienia do const, ani odniesienia rvalue) jako jedynej opcji, która (racjonalnie) niezawodnie produkuje to, czego większość ludzi normalnie chce.

 8
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-27 02:44:23

Częściowo dlatego, że zwracanie referencji do jaźni jest szybsze niż zwracanie przez wartość, ale dodatkowo, ma to pozwolić na oryginalną semantykę istniejącą w typach prymitywnych.

 4
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-23 21:52:12

operator= można zdefiniować, aby zwrócić cokolwiek chcesz. Musisz być bardziej dokładny co do tego, na czym polega problem; podejrzewam, że masz Konstruktor kopiujący używa operator= wewnętrznie i powoduje to przepełnienie stosu, ponieważ Konstruktor kopiujący wywołuje operator=, który musi użyć konstruktora kopiującego, aby zwrócić A przez wartość ad infinitum.

 4
Author: MSN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-23 22:07:09

Nie ma podstawowego wymogu językowego dla typu wynikowego zdefiniowanego przez użytkownika operator=, ale biblioteka standardowa ma taki wymóg:

C++98 §23.1/3:

" typ obiektów przechowywanych w tych komponentach musi spełniać wymagania CopyConstructible typy (20.1.3) oraz dodatkowe wymagania typów Assignable.

C++98 §23.1/4:

" w tabeli 64, T jest typem używanym do tworzenia instancji kontenera, t jest wartością T, a {[6] } jest wartością (ewentualnie const) T.

Tutaj wpisz opis obrazka


Zwracanie kopii według wartości nadal wspierałoby łańcuchowanie przypisań, tak jak a = b = c = 42;, ponieważ operator przypisania jest prawostronnie asocjacyjny, tzn. jest on przetwarzany jako a = (b = (c = 42));. Ale zwrócenie kopii zabraniałoby bezsensownych konstrukcji, takich jak (a = b) = 666;. Dla małej klasy zwracającej kopię może być najbardziej efektywna, podczas gdy dla większej klasy zwracającej przez odniesienie będzie ogólnie być najefektywniejszym (a Kopia, wręcz nieefektywnym).

Dopóki nie dowiedziałem się o standardowym wymaganiu biblioteki, pozwalałem operator= zwracać void, dla efektywności i uniknięcia absurdu wspierania złego kodu opartego na efektach ubocznych.


W C++11 istnieje dodatkowo wymóg T& typu wynikowego dla default-ING operatora przypisania, ponieważ

C++11 §8.4.2/1:

" funkcja, która jest jawnie defaulted [ ... ] mają ten sam zadeklarowany typ funkcji (z wyjątkiem ewentualnie różniących się kwalifikatorów ref i z tym, że w w przypadku konstruktora kopiującego lub operatora kopiowania typ parametru może być " reference to non-const T", Gdzie T jest nazwą klasy funkcji member) tak, jakby został niejawnie zadeklarowany

 3
Author: Cheers and hth. - Alf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-18 02:30:35