Ponieważ konstruktor kopiujący
MyClass(const MyClass&);
i przeciążenie operatora =
MyClass& operator = (const MyClass&);
mają prawie taki sam kod, ten sam parametr i różnią się tylko zwrotem, czy jest możliwe, aby obie miały wspólną funkcję?
c++
variable-assignment
copy-constructor
c++-faq
MPelletier
źródło
źródło
Odpowiedzi:
Tak. Istnieją dwie typowe opcje. Jednym - co jest ogólnie odradzane - jest
operator=
jawne wywołanie z konstruktora kopiującego:MyClass(const MyClass& other) { operator=(other); }
Jednak dostarczenie dobra
operator=
jest wyzwaniem, jeśli chodzi o radzenie sobie ze starym stanem i problemami wynikającymi z samozadania. Ponadto wszyscy członkowie i bazy są domyślnie inicjalizowane jako pierwsze, nawet jeśli mają być przypisane zother
. Może to nie dotyczyć nawet wszystkich członków i baz, a nawet jeśli jest poprawne, jest semantycznie nadmiarowe i może być praktycznie kosztowne.Coraz popularniejszym rozwiązaniem jest implementacja z
operator=
wykorzystaniem konstruktora kopiującego i metody swap.MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
lub nawet:
MyClass& operator=(MyClass other) { swap(other); return *this; }
swap
Funkcja jest zwykle prosty do napisania, jak to tylko zamienia własność wewnętrznych i nie trzeba oczyścić istniejącego stanu lub przeznaczyć nowe zasoby.Zaletą idiomu kopiowania i zamiany jest to, że jest on automatycznie bezpieczny przy samoczynnym przypisywaniu i - pod warunkiem, że operacja zamiany nie jest rzutowana - jest również silnie bezpieczna dla wyjątków.
Aby być silnie zabezpieczonym przed wyjątkami, operator przypisania „odręcznego” zazwyczaj musi przydzielić kopię nowych zasobów przed cofnięciem alokacji starych zasobów cesjonariusza, tak aby w przypadku wystąpienia wyjątku przy przydzielaniu nowych zasobów stary stan nadal mógł zostać przywrócony do . Wszystko to jest dostępne za darmo z funkcją kopiowania i wymiany, ale zwykle jest bardziej złożone, a zatem podatne na błędy, do zrobienia od zera.
Jedyną rzeczą, na którą należy uważać, jest upewnienie się, że metoda swap jest prawdziwą zamianą, a nie domyślną,
std::swap
która używa samego konstruktora kopiującego i operatora przypisania.Zwykle używany
swap
jest element członkowski .std::swap
działa i jest gwarantowany bez rzutowania w przypadku wszystkich podstawowych typów i typów wskaźników. Większość inteligentnych wskaźników można również zamienić z gwarancją braku rzucania.źródło
operator=
tworzenie allingów z kontrolera kopiującego jest w rzeczywistości dość złe, ponieważ najpierw inicjalizuje wszystkie wartości do pewnych wartości domyślnych, aby zaraz potem zastąpić je wartościami innego obiektu.assign
w niektórych przypadkach (dla lekkich klas) funkcja składowa była używana zarówno przez kontrolera kopiującego, jak i operatora przypisania. W innych przypadkach (przypadki wymagające dużej ilości zasobów / przypadków używania, uchwyt / treść) kopiowanie / zamiana jest oczywiście drogą.Konstruktor kopiujący wykonuje pierwszą inicjalizację obiektów, które wcześniej były pamięcią surową. Operator przypisania OTOH zastępuje istniejące wartości nowymi. Częściej niż nigdy wiąże się to z odrzuceniem starych zasobów (na przykład pamięci) i przydzieleniem nowych.
Jeśli istnieje podobieństwo między nimi, oznacza to, że operator przypisania wykonuje niszczenie i tworzenie kopii. Niektórzy programiści faktycznie implementowali przypisywanie poprzez niszczenie w miejscu, po którym następowało tworzenie kopii zapasowej. Jest to jednak bardzo zły pomysł. (A co, jeśli jest to operator przypisania klasy bazowej, który wywołał podczas przypisywania klasy pochodnej?)
Obecnie używa się tego, co zwykle uważa się za idiom kanoniczny
swap
zgodnie z sugestią Charlesa:MyClass& operator=(MyClass other) { swap(other); return *this; }
Używa konstrukcji kopiowania (uwaga, która
other
jest kopiowana) i niszczenia (jest niszczona na końcu funkcji) - i używa ich również we właściwej kolejności: konstrukcja (może zawieść) przed zniszczeniem (nie może zawieść).źródło
swap
zadeklarowaćvirtual
?Coś mnie niepokoi:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
Po pierwsze, czytanie słowa „zamień”, kiedy myślę „kopiuj”, irytuje mój zdrowy rozsądek. Kwestionuję też cel tej wymyślnej sztuczki. Tak, wszelkie wyjątki przy konstruowaniu nowych (kopiowanych) zasobów powinny mieć miejsce przed wymianą, co wydaje się bezpiecznym sposobem na upewnienie się, że wszystkie nowe dane zostały wypełnione przed ich uruchomieniem.
W porządku. A co z wyjątkami, które mają miejsce po zamianie? (gdy stare zasoby zostaną zniszczone, gdy tymczasowy obiekt wyjdzie poza zakres) Z punktu widzenia użytkownika przypisania operacja nie powiodła się, chyba że tak się nie stało. Ma to ogromny efekt uboczny: kopia faktycznie się wydarzyła. Nie udało się tylko wyczyścić zasoby. Stan obiektu docelowego został zmieniony, mimo że operacja wydaje się z zewnątrz nie powiodła się.
Dlatego proponuję zamiast „zamiany” zrobić bardziej naturalny „transfer”:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); transfer(tmp); return *this; }
Wciąż trwa konstrukcja tymczasowego obiektu, ale następną natychmiastową czynnością jest uwolnienie wszystkich bieżących zasobów miejsca docelowego przed przeniesieniem (i ZEROWANIE, aby nie zostały one podwójnie zwolnione) zasobów źródła do niego.
Zamiast {construct, move, destruct}, proponuję {construct, destruct, move}. Ruch, który jest najniebezpieczniejszą akcją, jest wykonywany jako ostatni, gdy wszystko inne zostanie ustalone.
Tak, niepowodzenie zniszczenia jest problemem w obu schematach. Dane są uszkodzone (skopiowane, jeśli nie sądziłeś, że są) lub utracone (uwolnione, gdy nie sądziłeś, że tak jest). Zgubiony jest lepszy niż zepsuty. Żadne dane nie są lepsze niż złe dane.
Transfer zamiast zamiany. W każdym razie to moja sugestia.
źródło
First, reading the word "swap" when my mind is thinking "copy" irritates
-> Jako pisarz w bibliotece zazwyczaj znasz powszechne praktyki (kopiowanie + zamiana), a sednem jestmy mind
. Twój umysł jest faktycznie ukryty za publicznym interfejsem. Na tym polega kod wielokrotnego użytku.