Wiem, że kompilator C ++ tworzy konstruktor kopiujący dla klasy. W takim przypadku musimy napisać konstruktor kopiujący zdefiniowany przez użytkownika? Czy możesz podać kilka przykładów?
c++
copy-constructor
penguru
źródło
źródło
Odpowiedzi:
Konstruktor kopiujący wygenerowany przez kompilator wykonuje kopiowanie według elementów członkowskich. Czasami to nie wystarcza. Na przykład:
class Class { public: Class( const char* str ); ~Class(); private: char* stored; }; Class::Class( const char* str ) { stored = new char[srtlen( str ) + 1 ]; strcpy( stored, str ); } Class::~Class() { delete[] stored; }
w tym przypadku kopiowanie
stored
elementu składowego nie spowoduje zduplikowania bufora (skopiowany zostanie tylko wskaźnik), więc pierwsza zniszczona kopia udostępniająca bufor zostaniedelete[]
pomyślnie wywołana, a druga będzie działać w niezdefiniowanym zachowaniu. Potrzebujesz konstruktora kopiującego do głębokiego kopiowania (oraz operatora przypisania).Class::Class( const Class& another ) { stored = new char[strlen(another.stored) + 1]; strcpy( stored, another.stored ); } void Class::operator = ( const Class& another ) { char* temp = new char[strlen(another.stored) + 1]; strcpy( temp, another.stored); delete[] stored; stored = temp; }
źródło
delete stored[];
i uważam, że powinno byćdelete [] stored;
std::string
. Ogólna idea jest taka, że tylko klasy narzędziowe, które zarządzają zasobami, muszą przeciążać Wielką Trójkę, a wszystkie inne klasy powinny po prostu używać tych klas narzędzi, eliminując potrzebę definiowania którejkolwiek z Wielkiej Trójki.Trochę się denerwuję, że
Rule of Five
nie przytoczono zasady z .Ta zasada jest bardzo prosta:
Jest jednak bardziej ogólna wskazówka, której należy przestrzegać, która wynika z potrzeby pisania kodu bezpiecznego dla wyjątków:
Tutaj
@sharptooth
kod jest nadal (w większości) w porządku, jednak gdyby miał dodać drugi atrybut do swojej klasy, to by tak nie było. Rozważ następującą klasę:class Erroneous { public: Erroneous(); // ... others private: Foo* mFoo; Bar* mBar; }; Erroneous::Erroneous(): mFoo(new Foo()), mBar(new Bar()) {}
Co się stanie, jeśli
new Bar
rzuci? Jak usunąć wskazany obiektmFoo
? Istnieją rozwiązania (try / catch ...), po prostu się nie skalują.Właściwym sposobem radzenia sobie z tą sytuacją jest użycie odpowiednich klas zamiast surowych wskaźników.
class Righteous { public: private: std::unique_ptr<Foo> mFoo; std::unique_ptr<Bar> mBar; };
Dzięki tej samej implementacji konstruktora (a właściwie używaniu
make_unique
) mam teraz wyjątek bezpieczeństwa za darmo !!! Czy to nie jest ekscytujące? A co najważniejsze, nie muszę już martwić się o odpowiedni destruktor! Muszę napisać własneCopy Constructor
iAssignment Operator
choć, bounique_ptr
nie definiuje tych operacji ... ale to nie ma znaczenia;)Dlatego
sharptooth
ponownie odwiedzono klasę:class Class { public: Class(char const* str): mData(str) {} private: std::string mData; };
Nie wiem jak Ty, ale moje jest dla mnie łatwiejsze;)
źródło
Mogę przypomnieć sobie z mojej praktyki i pomyśleć o następujących przypadkach, w których mamy do czynienia z jawnym zadeklarowaniem / zdefiniowaniem konstruktora kopiującego. Pogrupowałem sprawy w dwie kategorie
Poprawność / semantyka
W tej sekcji umieszczam przypadki, w których zadeklarowanie / zdefiniowanie konstruktora kopiującego jest niezbędne do poprawnego działania programów używających tego typu.
Po przeczytaniu tej sekcji dowiesz się o kilku pułapkach związanych z zezwoleniem kompilatorowi na samodzielne wygenerowanie konstruktora kopiującego. Dlatego, jak zauważył Seand w swojej odpowiedzi , zawsze można bezpiecznie wyłączyć kopiowalność dla nowej klasy i celowo włączyć ją później, gdy jest to naprawdę potrzebne.
Jak sprawić, by klasa nie była kopiowalna w C ++ 03
Zadeklaruj prywatny konstruktor kopiujący i nie dostarczaj dla niego implementacji (aby kompilacja nie powiodła się na etapie łączenia, nawet jeśli obiekty tego typu są kopiowane we własnym zakresie klasy lub przez jej znajomych).
Jak sprawić, by klasa nie była kopiowalna w C ++ 11 lub nowszym
Zadeklaruj konstruktora kopiującego z
=delete
at end.Shallow vs Deep Copy
Jest to najlepiej zrozumiany przypadek i właściwie jedyny wymieniony w innych odpowiedziach. shaprtooth został pokryty go całkiem dobrze. Chcę tylko dodać, że głębokie kopiowanie zasobów, które powinny być wyłączną własnością obiektu, może dotyczyć dowolnego typu zasobów, z których dynamicznie przydzielana pamięć jest tylko jednym rodzajem. W razie potrzeby może również wymagać głębokiego skopiowania obiektu
Obiekty samorejestracyjne
Rozważ klasę, w której wszystkie obiekty - bez względu na to, jak zostały zbudowane - MUSZĄ zostać w jakiś sposób zarejestrowane. Kilka przykładów:
Najprostszy przykład: utrzymywanie całkowitej liczby aktualnie istniejących obiektów. Rejestracja obiektu polega tylko na zwiększaniu licznika statycznego.
Bardziej złożonym przykładem jest pojedynczy rejestr, w którym przechowywane są odwołania do wszystkich istniejących obiektów tego typu (tak, aby powiadomienia mogły być dostarczane do wszystkich z nich).
Inteligentne wskaźniki liczone jako referencje można uznać za szczególny przypadek w tej kategorii: nowy wskaźnik „rejestruje się” we współdzielonym zasobie, a nie w rejestrze globalnym.
Taka operacja samodzielnej rejestracji musi zostać wykonana przez DOWOLNEGO konstruktora typu, a konstruktor kopiujący nie jest wyjątkiem.
Obiekty z wewnętrznymi odsyłaczami
Niektóre obiekty mogą mieć nietrywialną strukturę wewnętrzną z bezpośrednimi odsyłaczami między ich różnymi podobiektami (w rzeczywistości wystarczy jeden taki wewnętrzny odsyłacz, aby wywołać ten przypadek). Konstruktor kopiujący dostarczony przez kompilator przerwie wewnętrzne powiązania między obiektami , konwertując je na skojarzenia między obiektami .
Przykład:
struct MarriedMan; struct MarriedWoman; struct MarriedMan { // ... MarriedWoman* wife; // association }; struct MarriedWoman { // ... MarriedMan* husband; // association }; struct MarriedCouple { MarriedWoman wife; // aggregation MarriedMan husband; // aggregation MarriedCouple() { wife.husband = &husband; husband.wife = &wife; } }; MarriedCouple couple1; // couple1.wife and couple1.husband are spouses MarriedCouple couple2(couple1); // Are couple2.wife and couple2.husband indeed spouses? // Why does couple2.wife say that she is married to couple1.husband? // Why does couple2.husband say that he is married to couple1.wife?
Kopiowane mogą być tylko obiekty spełniające określone kryteria
Mogą istnieć klasy, w których obiekty można bezpiecznie kopiować w jakimś stanie (np. Domyślny stan skonstruowany) i nie można ich kopiować w inny sposób. Jeśli chcemy zezwolić na kopiowanie obiektów bezpiecznych do kopiowania, to - jeśli programujemy defensywnie - potrzebujemy sprawdzenia w czasie wykonywania w konstruktorze kopiującym zdefiniowanym przez użytkownika.
Podobiekty nie do kopiowania
Czasami klasa, która powinna być kopiowalna, agreguje niepodlegające kopiowaniu podobiekty. Zwykle dzieje się tak w przypadku obiektów ze stanem nieobserwowalnym (ten przypadek jest omówiony bardziej szczegółowo w sekcji „Optymalizacja” poniżej). Kompilator jedynie pomaga rozpoznać ten przypadek.
Quasi-kopiowalne podobiekty
Klasa, która powinna być kopiowalna, może agregować podobiekt typu quasi-kopiowalnego. Typ quasi-kopiowalny nie zapewnia konstruktora kopiującego w ścisłym tego słowa znaczeniu, ale ma inny konstruktor, który pozwala na utworzenie koncepcyjnej kopii obiektu. Przyczyną tworzenia typu quasi-kopiowalnego jest brak pełnej zgody co do semantyki kopiowania typu.
Ostateczna decyzja należy wtedy do użytkowników tego typu - kopiując obiekty, muszą oni jednoznacznie określić (poprzez dodatkowe argumenty) zamierzony sposób kopiowania.
W przypadku nieobronnego podejścia do programowania możliwe jest również, że obecny jest zarówno zwykły konstruktor kopiujący, jak i konstruktor quasi-kopiujący. Może to być uzasadnione, gdy w zdecydowanej większości przypadków należy zastosować jedną metodę kopiowania, aw rzadkich, ale dobrze zrozumiałych sytuacjach, należy zastosować alternatywne metody kopiowania. Wówczas kompilator nie będzie narzekał, że nie może niejawnie zdefiniować konstruktora kopiującego; wyłączną odpowiedzialnością użytkowników będzie zapamiętanie i sprawdzenie, czy podobiekt tego typu powinien zostać skopiowany za pomocą konstruktora quasi-kopiującego.
Nie kopiuj stanu, który jest silnie powiązany z tożsamością obiektu
W rzadkich przypadkach podzbiór obserwowalnego stanu obiektu może stanowić (lub być uważany) za nieodłączną część tożsamości obiektu i nie powinien być przenoszony na inne obiekty (chociaż może to być nieco kontrowersyjne).
Przykłady:
UID obiektu (ale ten również należy do przypadku "samorejestracji" z góry, ponieważ identyfikator należy uzyskać w akcie samorejestracji).
Historia obiektu (np. Stos Cofnij / Ponów) w przypadku, gdy nowy obiekt nie może dziedziczyć historii obiektu źródłowego, ale zamiast tego rozpoczynać się od pojedynczego elementu historii „ Skopiowano o <CZAS> z <OTHER_OBJECT_ID> ”.
W takich przypadkach konstruktor kopiujący musi pominąć kopiowanie odpowiednich podobiektów.
Wymuszanie poprawnego podpisu konstruktora kopiującego
Podpis konstruktora kopiującego dostarczonego przez kompilator zależy od tego, jakie konstruktory kopiujące są dostępne dla podobiektów. Jeśli co najmniej jeden podobiekt nie ma prawdziwego konstruktora kopiującego (pobierającego obiekt źródłowy przez stałe odniesienie), ale zamiast tego ma mutującego konstruktora kopiującego (pobierającego obiekt źródłowy przez niestałe odniesienie), kompilator nie będzie miał wyboru ale niejawnie zadeklarować, a następnie zdefiniować mutujący konstruktor kopiujący.
A co, jeśli „mutujący” konstruktor kopiujący typu podobiektu w rzeczywistości nie mutuje obiektu źródłowego (i został po prostu napisany przez programistę, który nie wie o
const
słowie kluczowym)? Jeśli nie możemy naprawić tego kodu przez dodanie brakującegoconst
, to inną opcją jest zadeklarowanie własnego konstruktora kopiującego zdefiniowanego przez użytkownika z poprawnym podpisem i popełnienie grzechu przejścia na plikconst_cast
.Kopiowanie przy zapisie (COW)
Kontener COW, który podaje bezpośrednie odniesienia do swoich danych wewnętrznych, MUSI zostać głęboko skopiowany w czasie konstruowania, w przeciwnym razie może zachowywać się jak uchwyt zliczający odniesienia.
Optymalizacja
W następujących przypadkach możesz chcieć / chcieć zdefiniować własny konstruktor kopiujący z powodów związanych z optymalizacją:
Optymalizacja struktury podczas kopiowania
Rozważmy kontener obsługujący operacje usuwania elementów, ale można to zrobić, po prostu oznaczając usunięty element jako usunięty, a następnie ponownie wykorzystaj jego gniazdo. Kiedy tworzona jest kopia takiego kontenera, sensowne może być skompaktowanie zachowanych danych zamiast zachowania „usuniętych” gniazd w takiej postaci, w jakiej są.
Pomiń kopiowanie stanu nieobserwowalnego
Obiekt może zawierać dane, które nie są częścią jego obserwowalnego stanu. Zwykle są to buforowane / zapamiętywane dane gromadzone przez cały okres istnienia obiektu w celu przyspieszenia niektórych powolnych operacji zapytań wykonywanych przez obiekt. Można bezpiecznie pominąć kopiowanie tych danych, ponieważ zostaną one ponownie obliczone, gdy (i jeśli!) Zostaną wykonane odpowiednie operacje. Kopiowanie tych danych może być nieuzasadnione, ponieważ może zostać szybko unieważnione, jeśli obserwowalny stan obiektu (z którego pochodzą dane z pamięci podręcznej) zostanie zmodyfikowany przez operacje mutacyjne (a jeśli nie zamierzamy modyfikować obiektu, to dlaczego tworzymy głęboki skopiować?)
Optymalizacja ta jest uzasadniona tylko wtedy, gdy dane pomocnicze są duże w porównaniu z danymi reprezentującymi obserwowalny stan.
Wyłącz niejawne kopiowanie
C ++ umożliwia wyłączenie niejawnego kopiowania poprzez zadeklarowanie konstruktora kopiującego
explicit
. Wówczas obiekty tej klasy nie mogą być przekazywane do funkcji i / lub zwracane z funkcji przez wartość. Ta sztuczka może być użyta dla typu, który wydaje się lekki, ale w rzeczywistości jest bardzo drogi do skopiowania (chociaż uczynienie go quasi-kopiowalnym może być lepszym wyborem).TODOs
źródło
Jeśli masz klasę, która ma dynamicznie przydzielaną zawartość. Na przykład przechowujesz tytuł książki jako znak * i ustawiasz tytuł na nowy, kopiowanie nie będzie działać.
Musiałbyś napisać konstruktora kopiującego, który to robi,
title = new char[length+1]
a potemstrcpy(title, titleIn)
. Konstruktor kopiujący wykonałby po prostu „płytką” kopię.źródło
Copy Constructor jest wywoływana, gdy obiekt jest przekazywany przez wartość, zwracany przez wartość lub jawnie kopiowany. Jeśli nie ma konstruktora kopiującego, c ++ tworzy domyślny konstruktor kopiujący, który tworzy płytką kopię. Jeśli obiekt nie ma wskaźników do dynamicznie przydzielanej pamięci, zrobi to płytka kopia.
źródło
Często dobrym pomysłem jest wyłączenie ctor kopiowania i operator =, chyba że klasa wyraźnie tego potrzebuje. Może to zapobiec nieefektywności, takim jak przekazywanie argumentu przez wartość, gdy jest zamierzone odniesienie. Również metody wygenerowane przez kompilator mogą być nieprawidłowe.
źródło
Rozważmy poniższy fragment kodu:
class base{ int a, *p; public: base(){ p = new int; } void SetData(int, int); void ShowData(); base(const base& old_ref){ //No coding present. } }; void base :: ShowData(){ cout<<this->a<<" "<<*(this->p)<<endl; } void base :: SetData(int a, int b){ this->a = a; *(this->p) = b; } int main(void) { base b1; b1.SetData(2, 3); b1.ShowData(); base b2 = b1; //!! Copy constructor called. b2.ShowData(); return 0; }
Output: 2 3 //b1.ShowData(); 1996774332 1205913761 //b2.ShowData();
b2.ShowData();
daje niepotrzebne dane wyjściowe, ponieważ istnieje konstruktor kopiujący zdefiniowany przez użytkownika, który nie został napisany w celu jawnego kopiowania danych. Więc kompilator nie tworzy tego samego.Pomyślałem o podzieleniu się tą wiedzą z wami wszystkimi, chociaż większość z was już to wie.
Pozdrawiam ... Miłego kodowania !!!
źródło