2010-03-05 13 views

Odpowiedz

10

Jeśli twoja klasa zawiera tylko obiekty wektorowe/łańcuchowe jako elementy danych, nie musisz ich implementować. Klasy C++ STL (jak wektor, łańcuch znaków) mają swój własny ctor, przeciążony operator przypisania i destruktor.

Ale jeśli twoja klasa dynamicznie alokuje pamięć w konstruktorze to naiwna płytka kopia będzie powodować kłopoty. W takim przypadku będziesz musiał zaimplementować ctor ctor, przeciążony operator przypisania i destruktor.

0

Musisz je podać, jeśli ich potrzebujesz. lub ewentualni użytkownicy twoich zajęć. destructor jest zawsze musi, a konstruktory kopiowania i operator przypisania są automatycznie tworzone przez kompilator. (Przynajmniej MSVC)

+1

Destruktor jest również automatyczny (kompilator nie sprawi, że będą one * wirtualne *, ale jest to inny problem). – visitor

5

Zwykła zasada mówi: jeśli potrzebujesz jednego z nich, potrzebujesz ich wszystkich.

Nie wszystkie klasy ich potrzebują. Jeśli klasa nie posiada żadnych zasobów (przede wszystkim pamięci), bez nich wszystko będzie dobrze. Na przykład klasa z pojedynczym składnikiem string lub vector tak naprawdę ich nie potrzebuje - chyba że potrzebujesz jakiegoś specjalnego zachowania kopiowania (domyślnie po prostu skopiuje elementy).

+0

Zamiast mówić: "nie wszystkie klasy ich potrzebują", czy nie byłoby dokładniej powiedzieć "zachowanie domyślnego konstruktora kopiowania, destruktora i operatora przypisania będzie w porządku."? (Oznacza to, że nie będzie trzeba zastępować ustawień domyślnych własnymi implementacjami). – DavidRR

4

Domyślny konstruktor kopiowania skopiuje wektor, jeżeli jest zadeklarowany przez wartość. Uważaj, jeśli zapisałeś wskaźniki w swoim wektorze, w takim przypadku musisz podać określone zachowanie dla kopiowania/przydzielania/niszczenia, aby uniknąć wycieków pamięci lub wielokrotnego usunięcia.

1

Kontener ten będzie wymagał elementu "konstruktywnego kopiowania", a jeśli nie dostarczysz konstruktora kopiowania, wywoła domyślnego konstruktora kopii twojej klasy, wywnioskując od członków twojej klasy (płytka kopia).

proste wyjaśnienie dotyczące domyślnego konstruktora kopii jest tutaj: http://www.fredosaurus.com/notes-cpp/oop-condestructors/copyconstructors.html

to jest tak ze destructor, pojemnik musi mieć dostęp do destructor lub domyślnej klasy destruktora jeśli nie zapewnia jeden (czyli to będzie. nie działa, jeśli deklarujesz destruktor jako prywatny)

+0

Znaleziono informacje w dołączonym łączu bardzo pomocne. – DavidRR

0

Kiedy masz klasę wymagającą głębokich kopii, powinieneś je zdefiniować.

W szczególności, każda klasa, która zawiera wskaźniki lub referencje powinny zawierać im takich jak:

class foo { 
private: 
    int a,b; 
    bar *c; 
} 

subiektywnie, powiedziałbym, zawsze je zdefiniować jako zachowanie domyślne dostarczone przez kompilator generowane wersji nie może być to, czego oczekiwać/chcieć.

+1

Może być lepiej powiedzieć: jeśli klasa * posiada * zasób. W rzeczywistości instancja 'bar' wskazuje, że' c' może być własnością i jest kontrolowana gdzie indziej, a 'foo' jest po prostu współdzielącym użytkownikiem obiektu. - Co ciekawe, poleciłbym także * nie *, aby je zdefiniować, jeśli domyślnie jest OK: znacznie częściej popełniasz błędy niż kompilator i przerwiesz kopiowanie i przypisywanie (a w destruktorze nie ma nic do zrobienia w pierwszej kolejności w takim przypadku). – visitor

+0

@visitor: patrz odpowiedź Lilburne'a - w zasadzie jest taka sama, ale bardziej szczegółowa z powodów - subiektywnie, czuję, że ma rację. –

+0

Naturalnie potrzebujesz ich, jeśli chcesz czegoś poza płytką, zgodną z oryginałem. Ale nie jestem do końca przekonany, dlaczego powinieneś zrobić to ręcznie dla kopii zgodnych z zasadami (co jest dla mnie większością klas, jeśli mają być kopiowane w pierwszej kolejności) - jeśli to nie jest to, czego oczekujesz, może ty oczekuj bardzo dziwnej semantyki od kopiowania. - Być może obiektywnym powodem ręcznego wpisania operatora przypisania jest zapewnienie silniejszych gwarancji wyjątków (lhv niezmieniony, nie tylko brak wycieku pamięci), ale przypuszczam, że byłoby to bardzo trudne (trzeba cofnąć zmiany), aby zrobić to powszechnie. – visitor

0

Nie dla łańcuchów lub wektorów, ponieważ trywialne konstruktory/destruktory itp. Będą działać poprawnie.

Jeśli klasa ma wskaźniki do innych danych i potrzebuje głębokich kopii lub jeśli klasa posiada zasób, który musi zostać zwolniony lub musi zostać skopiowany w specjalny sposób.

2

Nie, ale istnieje wiele powodów, dla których nie należy zezwalać kompilatorowi na automatyczne generowanie tych funkcji.

Z mojego doświadczenia wynika, że ​​najlepiej jest zdefiniować je samemu i nabrać nawyku upewniania się, że są one zachowane, gdy zmieniasz klasę. Po pierwsze, możesz chcieć postawić punkt przerwania, gdy zostanie wywołany konkretny ctor lub dtor. Również ich nie zdefiniowanie może spowodować nadpisanie kodu, ponieważ kompilator wygeneruje połączenia inline do ctor elementu i dtor (Scott Meyers ma sekcję na ten temat).

Czasami chcesz również zablokować domyślne uprawnienia do kopiowania i przypisania. Na przykład mam aplikację, która przechowuje i manipuluje bardzo dużymi blokami danych. Rutynowo mamy ekwiwalent wektora STL zawierającego miliony punktów 3D i byłoby katastrofą, gdybyśmy pozwolili na kopiowanie tych kontenerów. Tak więc operatorzy citor i przypisania są zadeklarowani jako prywatni i nie zdefiniowani. W ten sposób, jeśli ktokolwiek napisze , otrzyma błąd kompilatora. Nasze doświadczenie jest takie, że metoda explicit() lub clone() jest znacznie mniej podatna na błędy.

W sumie istnieje wiele powodów, aby unikać generowania automatycznie generowanych funkcji kompilatora.

+0

powinno "nie" być 'Uwaga'? – Bill

+1

"nabrać nawyku upewniania się, że są one zachowane, gdy zmieniasz klasę". To niepotrzebny koszmar utrzymania. – fredoverflow

+0

Nie powinieneś mieć testów jednostkowych dla swoich lekarzy itp., Aby sprawdzić poprawność inicjalizacji? Czy nie należy brać pod uwagę wszystkich konsekwencji dodawania członków danych do zajęć? Jeśli dodajesz nowy ciąg do klasy, jaki jest wpływ na nadpisywanie kodu we wszystkich metodach, które go używają, oraz we wszystkich klasach, które mogą zawierać jego instancje? Po dodaniu nowego członka nie musisz ponownie zastanawiać się, czy zezwolenie na autogenerację jest już możliwe? Podczas gdy zastanawiasz się nad tymi wszystkimi rzeczami, które dodają do ctor-ctor i op = jest minimalne. – lilburne

2

Mogę wymyślić kilka przypadków, kiedy trzeba napisać własną Wielką Trójkę. Wszystkie standardowe pojemniki potrafią kopiować i niszczyć, więc niekoniecznie musisz je pisać. Oto, jak się dowiedzieć, kiedy to zrobisz:

Czy moja klasa ma jakieś zasoby?

Domyślne semantyka kopia dla wskaźników jest skopiowanie wartościwskaźnika, co nie wskazuje. Jeśli chcesz coś głęboko skopiować, nawet jeśli jest on przechowywany w standardowym pojemniku, musisz napisać własny konstruktor kopii i operator przypisania. Musisz również napisać własny destruktor, aby właściwie uwolnić te zasoby.

Czy ktoś może odziedziczyć po mojej klasie?

Klasy podstawowe wymagają destruktora. Herb Sutter zaleca, aby je albo public i virtual (najczęstszy przypadek) lub protected i nie-wirtualny, w zależności od tego, co chcesz z nimi zrobić. Destruktor generowany przez kompilator jest publiczny i nie-wirtualny, więc musisz napisać własny, nawet jeśli nie ma w nim żadnego kodu. (Uwaga: nie oznacza to trzeba napisać konstruktora kopia lub operatora przypisania.)

Mam uniemożliwić użytkownikowi kopiowanie obiektów mojej klasy?

Jeśli nie chcesz, aby użytkownik kopiować przedmiotów (może to jest zbyt drogie), trzeba zadeklarować operatorów konstruktor kopiujący i przypisania albo protected lub private. Nie musisz ich implementować, chyba że ich potrzebujesz. (Uwaga: nie oznacza to, że musisz napisać destruktor.)

Konkluzja:

Najważniejszą rzeczą jest, aby zrozumieć, co konstruktor kompilator generowane kopiowanie, operator przypisania, a destructor zrobi. Nie musisz się ich bać, ale musisz o nich pomyśleć i zdecydować, czy ich zachowanie jest odpowiednie dla twojej klasy.

Powiązane problemy