2009-11-14 9 views
70

Od konstruktora kopiiKopiuj konstruktor i = przeciążenie operatora w C++: czy możliwa jest wspólna funkcja?

MyClass(const MyClass&); 

i przeciążenia = operatora

MyClass& operator = (const MyClass&); 

mają prawie ten sam kod, ten sam parametr, a różnią się tylko w zamian, jest to możliwe, aby mieć wspólny funkcja dla nich obu używać?

+3

"... mają prawie taki sam kod ..."? Hmm ... Musisz zrobić coś złego. Postaraj się zminimalizować potrzebę korzystania z funkcji zdefiniowanych przez użytkownika i pozwól, aby kompilator wykonał całą brudną robotę. Często oznacza to hermetyzowanie zasobów w ich własnym obiekcie członkowskim. Możesz pokazać nam jakiś kod. Może mamy jakieś dobre sugestie dotyczące projektu. – sellibitze

+1

Możliwy duplikat [Zmniejszenie duplikacji kodu pomiędzy operatorem = a konstruktorem kopiującym] (http://stackoverflow.com/questions/1477145/reducing-code-code-duplication-between-operator-and-copy-constructor) – mpromonet

Odpowiedz

96

Tak. Istnieją dwie powszechne opcje. One - czego nie polecam - jest wywołanie operator= z konstruktora kopii extenso:

MyClass(const MyClass& other) 
{ 
    operator=(other); 
} 

Jednak, zapewniając dobry operator= jest wyzwaniem jeśli chodzi o czynienia ze starym stan i problemy wynikające z pracy na własny zadanie. Ponadto, wszyscy członkowie i bazy są domyślnie inicjowani jako pierwsze, nawet jeśli mają być przypisani do domeny od other. Może to nawet nie być ważne dla wszystkich członków i baz, a nawet tam, gdzie jest to ważne, jest semantycznie zbędne i może być praktycznie kosztowne.

Coraz popularniejszym rozwiązaniem jest implementacja operator= przy użyciu konstruktora kopiowania i metody zamiany.

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.

Zalety idiomu kopiowania i wymiany są takie, że jest on samoczynnie bezpieczny i - pod warunkiem, że operacja zamiany nie jest rzutowaniem - jest również wyjątkowo bezpieczny pod względem wyjątków.

Aby być wyjątkowo bezpiecznym w wyjątkach, "ręczny" operator przypisania zazwyczaj musi przydzielić kopię nowych zasobów przed cofnięciem starych zasobów cesjonariusza, tak aby w przypadku wyjątku przy alokowaniu nowych zasobów stare państwo mogło nadal być zwrócone. Wszystko to odbywa się za darmo przy użyciu funkcji kopiowania i wymiany, ale zwykle jest bardziej skomplikowane, a zatem podatne na błędy, od zera.

Należy się upewnić, że metoda wymiany jest prawdziwą zamianą, a nie domyślną, która używa konstruktora kopiowania i samego operatora przypisania.

Zazwyczaj używany jest element członkowski swap. std::swap działa i jest gwarantowana "no-throw" dla wszystkich podstawowych typów i typów wskaźników. Większość inteligentnych wskaźników można również zamieniać z gwarancją braku rzutu.

+3

Właściwie nie są to częste operacje. Podczas pierwszego wykonywania inicjalizacji elementów obiektu operator przypisania nadpisuje istniejące wartości. Biorąc to pod uwagę, alling 'operator =' od ctor ctor jest w rzeczywistości bardzo zły, ponieważ najpierw inicjalizuje wszystkie wartości do niektórych wartości domyślnych po prostu nadpisując je wartościami innych obiektów zaraz potem. – sbi

+0

Powiedziałem o wspólnych opcjach, a nie operacjach. Całkowicie zgadzam się, że wywoływanie 'operator =' z konstruktora kopiowania nie jest dobre, ale wystarczy spojrzeć na uzasadniony kod realnego świata, aby zobaczyć, jak często to jest. –

+0

Downwotarzy, czy chcesz wyjaśnić? –

11

Konstruktor kopiowania wykonuje pierwszą inicjalizację obiektów, które poprzednio były pamięcią pierwotną. Operator przypisania OTOH nadpisuje istniejące wartości nowymi. Częściej niż nigdy, obejmuje to odwoływanie starych zasobów (na przykład pamięci) i przydzielanie nowych.

Jeśli istnieje podobieństwo między tymi dwoma, to operator przypisania wykonuje zniszczenie i tworzenie kopii. Niektórzy programiści rzeczywiście implementowali przypisanie przez zniszczenie na miejscu, a następnie tworzenie kopii miejsc docelowych. Jest to jednak zły pomysł.(Co jeśli to jest operator przypisania klasy bazowej, który wywołał podczas przypisania klasy pochodnej?)

Co zwykle uważane za idiom kanoniczna w dzisiejszych czasach używa swap jak Charles zasugerował:

MyClass& operator=(MyClass other) 
{ 
    swap(other); 
    return *this; 
} 

ta wykorzystuje kopię -konstrukcja (zauważ, że skopiowana jest other) i zniszczenie (jest niszczona na końcu funkcji) - i używa ich również we właściwej kolejności: konstrukcja (może się nie powieść) przed zniszczeniem (nie może się nie powieść).

+0

Czy 'swap' powinien być" wirtualny "? – nonplus

+1

@Johannes: Funkcje wirtualne są używane w hierarchiach klas polimorficznych. Operatory przypisania są używane dla typów wartości. Dwóch prawie nie mieszają się. – sbi

-2

Coś przeszkadza mi o:

MyClass& operator=(const MyClass& other) 
{ 
    MyClass tmp(other); 
    swap(tmp); 
    return *this; 
} 

pierwsze czytanie słowa „swap”, kiedy mój umysł jest myślenie „kopię” drażni mój zdrowy rozsądek. Ponadto podważam cel tej fantazyjnej sztuczki. Tak, wszelkie wyjątki w konstruowaniu nowych (kopiowanych) zasobów powinny się zdarzyć przed wymianą, co wydaje się bezpiecznym sposobem upewnienia się, że wszystkie nowe dane są wypełnione przed uruchomieniem.

W porządku. A co z wyjątkami, które nastąpią po wymianie? (gdy stare zasoby zostaną zniszczone, gdy tymczasowy obiekt znajdzie się poza zakresem) Z punktu widzenia użytkownika przypisania, operacja się nie powiodła, z wyjątkiem tego, że nie. Ma ogromny efekt uboczny: kopia rzeczywiście się wydarzyła. To tylko niektóre czyszczenia zasobów, które zawiodły. Stan obiektu docelowego został zmieniony, mimo że operacja wydawała się z zewnątrz nieudana.

Więc proponuję zamiast „swap” do zrobienia bardziej naturalny „Transfer”:

MyClass& operator=(const MyClass& other) 
{ 
    MyClass tmp(other); 
    transfer(tmp); 
    return *this; 
} 

Wciąż budowy tymczasowego obiektu, ale obok jest natychmiastowe działanie, aby uwolnić wszystkie aktualne zasoby miejsce docelowe przed przeniesieniem (i NULLing, aby nie zostały uwolnione podwójnie) zasobów źródła do niego.

Zamiast {konstruuj, przenoś, niszcz,} proponuję {konstruuj, niszcz, przesuwaj}. Ruch, który jest najniebezpieczniejszą czynnością, jest tym, który został zrobiony ostatni po tym, jak wszystko inne zostało ustalone.

Tak, niepowodzenie destrukcji jest problemem w obu systemach. Dane są uszkodzone (skopiowane, gdy nie sądzisz, że tak się stało) lub utracone (uwolnione, gdy nie myślałeś, że to było). Utracone jest lepsze niż uszkodzone. Żadne dane nie są lepsze niż złe dane.

Przelew zamiast zamiany. Taka jest moja sugestia.

+2

Niszczyciel nie może zawieść, więc nie oczekuje się wyjątków po zniszczeniu. I, nie rozumiem, co byłoby zaletą przesunięcia ruchu za zniszczeniem, jeśli ruch jest najbardziej niebezpieczną operacją? Oznacza to, że w schemacie standardowym niepowodzenie przeniesienia nie uszkodzi starego stanu, podczas gdy twój nowy system nie. Więc dlaczego? Ponadto, "Po pierwsze, czytanie słowa" zamień ", gdy mój umysł myśli" kopiuj "irytuje' -> Jako pisarz biblioteczny zwykle znasz znane praktyki (kopiowanie i zamiana), a sedno to" mój umysł ". Twój umysł jest ukryty za publicznym interfejsem. O to właśnie chodzi w przypadku kodu wielokrotnego użytku. –

Powiązane problemy