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.