Što je pravilo tri?

  • Što znači kopiranje objekta?
  • Što je konstruktor kopiranja i izjava o dodjeli kopiranja?
  • Kada ih moram osobno proglasiti?
  • Kako mogu spriječiti kopiranje objekata?
1911
13 нояб. postavila fredoverflow 13. studenog 2010-11-13 16:27 '10 u 16:27 2010-11-13 16:27
@ 8 odgovora

uvod

C ++ obrađuje prilagođene varijable tipa s vrijednostima semantike. To znači da se objekti implicitno kopiraju u različitim kontekstima, i moramo razumjeti što zapravo znači "kopiranje objekta".

Razmotrite jednostavan primjer:

 class person { std::string name; int age; public: person(const std::string name, int age) : name(name), age(age) { } }; int main() { person a("Bjarne Stroustrup", 60); person b(a); // What happens here? b = a; // And here? } 

(Ako ste zbunjeni glede name(name), age(age) dijelova, to se naziva popis inicijatora članova .)

Posebne funkcije člana

Što znači kopiranje person ? main funkcija prikazuje dva različita skripta za kopiranje. Inicijalna person b(a); izvršava konstruktor kopiranja. Njegov je zadatak izgraditi novi objekt na temelju stanja postojećeg objekta. Dodjeljivanje b = a obavlja operator za dodjelu kopija. Njegov rad je obično malo teži, jer je ciljni objekt već u nekom prihvatljivom stanju koje treba riješiti.

Budući da nismo deklarirali ni konstruktor kopiranja, ni operator dodjeljivanja (ili destructor), oni su implicitno definirani za nas. Citat iz standarda:

Konstruktor kopiranja [...] i operator kopiranja, [...] i destruktor su posebne funkcije člana. [Napomena: Implementacija će implicitno deklarirati te funkcije člana za određene tipove klasa kada ih program izričito ne deklarira. Implementacija će ih implicitno definirati ako se koriste. [...] završna napomena] [n3126.pdf odjeljak 12 §1]

Po zadanom, kopiranje objekta znači kopiranje njegovih elemenata:

Implicitno definirani konstruktor kopiranja za klasu X koja nije jedinica, izvodi faznu kopiju svojih pod-objekata. [n3126.pdf odjeljak 12.8 §16]

Implicitno dodijeljeni operator dodjeljivanja kopija za klasu ne-jedinice X obavlja faznu dodjelu kopije svojih pod-objekata. [n3126.pdf odjeljak 12.8 §30]

Implicitne definicije

Implicitno definirane posebne funkcije člana za person su sljedeće:

 // 1. copy constructor person(const person that) : name(that.name), age(that.age) { } // 2. copy assignment operator person operator=(const person that) { name = that.name; age = that.age; return *this; } // 3. destructor ~person() { } 

U ovom slučaju, želimo kopirati ono što želimo: name i age kopiraju, tako da dobivamo samostalni, neovisni objekt. Implicitno definirani destruktor je uvijek prazan. Ovo je također veliko u ovom slučaju, budući da nismo dobili nikakve resurse u konstruktoru. Članovi destruktori implicitno se pozivaju nakon što person završi destruktor:

Nakon izvršavanja tijela destruktora i uništavanja bilo kojih automatskih objekata dodijeljenih u tijelu, destruktor klase X uzrokuje destruktore za X izravne članove [n3126.pdf 12.4 §6]

Upravljanje resursima

Dakle, kada trebamo eksplicitno proglasiti ove posebne članske funkcije? Kada naš razred upravlja resursom, to jest, kada je objekt klase odgovoran za taj resurs. To obično znači da se resurs stječe u konstruktoru (ili prosljeđuje konstruktoru) i oslobađa u destruktoru.

Vratimo se na preliminarni standard C ++. std::string nije postojalo, a programeri su bili zaljubljeni u pokazivače. Klasa person može izgledati ovako:

 class person { char* name; int age; public: // the constructor acquires a resource: // in this case, dynamic memory obtained via new[] person(const char* the_name, int the_age) { name = new char[strlen(the_name) + 1]; strcpy(name, the_name); age = the_age; } // the destructor must release this resource via delete[] ~person() { delete[] name; } }; 

Čak i danas ljudi još uvijek pišu nastavu u ovom stilu i upadaju u nevolje: "Gurnuo sam osobu u vektor, a sada dobivam lude pogreške u pamćenju!" Zapamtite da kopiranje objekta podrazumijeva kopiranje njegovih elemenata, ali kopiranje name člana jednostavno kopira pokazivač, a ne niz znakova na koje ukazuje! To ima nekoliko neugodnih učinaka:

  • Promjene kroz a mogu se promatrati kroz b .
  • Čim je b uništen, a.name je a.name pokazivač.
  • Ako a uništi, uklanjanje slomljenog pokazivača daje nedefinirano ponašanje .
  • Budući da zadatak ne uzima u obzir ono što je name navedeno prije zadatka, prije ili kasnije ćete dobiti curenje memorije svugdje.

Eksplicitne definicije

Budući da kopiranje u redoslijedu nema željeni učinak, moramo izričito definirati konstrukt kopiranja i operatora dodjeljivanja kopija za izradu dubokih kopija niza znakova:

 // 1. copy constructor person(const person that) { name = new char[strlen(that.name) + 1]; strcpy(name, that.name); age = that.age; } // 2. copy assignment operator person operator=(const person that) { if (this !=  { delete[] name; // This is a dangerous point in the flow of execution! // We have temporarily invalidated the class invariants, // and the next statement might throw an exception, // leaving the object in an invalid state :( name = new char[strlen(that.name) + 1]; strcpy(name, that.name); age = that.age; } return *this; } 

Primijetite razliku između inicijalizacije i dodjele: moramo povući staro stanje prije dodjeljivanja name kako bi se spriječilo curenje memorije. Osim toga, moramo zaštititi oblik x = x od samosvijesti. Bez ove provjere, delete[] name će izbrisati polje koje sadrži izvorni niz, jer kada pišete x = x , i that.name this->name i that.name sadrže isti pokazivač.

Sigurnost izuzeća

Nažalost, ovo rješenje neće uspjeti ako new char[...] izbaci iznimku zbog iscrpljenosti memorije. Jedno od mogućih rješenja je uvesti lokalnu varijablu i promijeniti redoslijed operatora:

 // 2. copy assignment operator person operator=(const person that) { char* local_name = new char[strlen(that.name) + 1]; // If the above statement throws, // the object is still in the same state as before. // None of the following statements will throw an exception :) strcpy(local_name, that.name); delete[] name; name = local_name; age = that.age; return *this; } 

Također osigurava samo-dodjeljivanje bez eksplicitne provjere. Još pouzdanije rješenje ovog problema je idiom kopiranja i zamjene , ali neću ulaziti u detalje sigurnosnih iznimaka. Spomenuo sam samo iznimke za sljedeće: Pisanje klasa koje upravljaju resursima je teško.

Resursi koji se ne mogu kopirati

Neki se resursi ne mogu ili ne smiju kopirati, na primjer, deskriptori datoteka ili mutex-ovi. U tom slučaju, jednostavno izgovorite konstruktora kopiranja i operatora odredišta kopiranja kao private bez navođenja definicije:

 private: person(const person that); person operator=(const person that); 

Alternativno, možete naslijediti iz boost::noncopyable ili ih deklarirati kao izbrisane (C ++ 0x):

 person(const person that) = delete; person operator=(const person that) = delete; 

Pravilo tri

Ponekad morate implementirati klasu koja upravlja resursom. (Nikada ne upravljajte višestrukim resursima u istom razredu, to će samo dovesti do boli.) U ovom slučaju, zapamtite pravilo tri :

Ako trebate izričito deklarirati destruktor, kopirajte konstruktor ili izjavu o zadatku kopiranja, vjerojatno trebate izričito proglasiti sve tri.

(Nažalost, ovo "pravilo" ne primjenjuje C ++ standard ili prevodilac, za koji znam.)

Odbor

U većini slučajeva ne morate sami upravljati resursom, jer postojeća klasa, kao što je std::string , to već čini za vas. Jednostavno usporedite jednostavan kod s članom std::string na zbunjujuću i sklonu pogreškama alternativu koja koristi char* , i trebali biste biti sigurni. Dokle god se držite podalje od sirovih pokazivača, pravilo troje jedva vrijedi za vaš vlastiti kod.

1561
13 нояб. odgovor je dao fredoverflow 13. studenog 2010-11-13 16:27 '10 u 16:27 2010-11-13 16:27

Pravilo tri je opće pravilo za C ++, općenito govoreći

Ako vašoj klasi treba bilo koja

  • konstruktor za kopiranje ,
  • operator dodjeljivanja ,
  • ili destruktor ,

određuje se eksplicitno, onda će vjerojatno sva tri biti potrebna.

Razlozi za to su da se sva tri obično koriste za upravljanje resursom, a ako vaš razred upravlja resursom, obično treba upravljati kopiranjem, kao i da ga oslobodi.

border=0

Ako nema dobre semantike za kopiranje resursa kojima upravlja klasa, razmislite o zabrani kopiranja deklariranjem (ne definiranjem ) konstruktora kopiranja i operatora dodjele kao private .

(Imajte na umu da nadolazeća nova verzija C ++ standarda (koji je C ++ 11) dodaje semantiku pomaka u C ++, što će najvjerojatnije promijeniti pravilo tri.Ali, ipak znam premalo o tome da bih napisao C + +11 odjeljak o pravilu tri.)

467
13 нояб. Odgovor daje sbi 13. studenog. 2010-11-13 17:22 '10 u 17:22 2010-11-13 17:22

Zakon velike tri je kao što je gore navedeno.

Jednostavan primjer, na jednostavnom engleskom jeziku, problema koji rješava:

Custom Destructor

U konstruktoru ste dodijelili memoriju i trebate napisati destructor da biste ga uklonili. Inače će doći do curenja memorije.

Možda misliš da je ovo posao.

Problem će biti ako je kopija izrađena od vašeg objekta, a kopija će pokazivati ​​istu memoriju kao i izvorni objekt.

Čim jedan od njih ukloni memoriju u svom destruktoru, drugi će imati pokazivač na neispravnu memoriju (to se naziva viseći pokazivač), kada ga pokuša koristiti, sve će izgledati dlakavo.

Prema tome, pišete konstruktor za kopiranje tako da raspoređuje nove objekte kako bi uništili vlastite fragmente memorije.

Operator dodjele i konstruktor kopiranja

Dodijelili ste memoriju u konstruktoru pokazivaču na člana vaše klase. Kada kopirate objekt ove klase, zadani izraz pridruživanja i konstruktor kopiranja kopirat će vrijednost ovog elementa pokazivača u novi objekt.

To znači da će novi objekt i stari objekt ukazati na isti dio memorije, tako da kada ga promijenite u jedan objekt, bit će promijenjen za drugi objekt. Ako jedan objekt ukloni ovu memoriju, drugi će je pokušati koristiti - eek.

Da biste riješili ovaj problem, napišite vlastitu verziju konstruktora kopiranja i operatora dodjele. Vaše verzije dodjeljuju zasebnu memoriju novim objektima i kopiraju vrijednosti na koje upućuje prvi pokazivač, a ne njegovu adresu.

143
14 мая '12 в 17:22 2012-05-14 17:22 je odgovorio / la Stefan na May 14, '12 u 5:22 PM 2012-05-14 17:22

U osnovi, ako imate destructor (a ne default destructor), to znači da klasa koju ste definirali ima neku dodijeljenu memoriju. Pretpostavimo da se klasa koristi izvan nekog koda klijenta ili od vas.

  MyClass x(a, b); MyClass y(c, d); x = y; // This is a shallow copy if assignment operator is not provided 

Ako MyClass ima samo neke primitivne tipizirane članove, izvršit će se zadani izraz pridruživanja, ali ako ima neke elemente pokazivača i objekte koji nemaju izjave zadatka, rezultat će biti nepredvidljiv. Stoga, možemo reći da ako postoji nešto što treba ukloniti u klasi destructor, možda će nam trebati operator dubokog kopiranja, što znači da moramo osigurati konstruktora kopiranja i operatora dodjele.

39
31 дек. odgovor dat fatma.ekici 31 prosinca 2012-12-31 22:29 '13 u 22:29 2012-12-31 22:29

Što znači kopiranje objekta? Postoji nekoliko načina za kopiranje objekata - recite nam o dvije vrste na koje se najvjerojatnije radi: duboku kopiju i plitku kopiju.

Budući da smo u objektno orijentiranom jeziku (ili barem pretpostavimo), recimo da imate namjenski dio memorije. Budući da je ovo OO jezik, lako se možemo pozvati na dijelove memorije koje dodjeljujemo, jer su to obično primitivne varijable (ints, chars, bytes) ili klase koje smo definirali i koje su napravljene od naših vlastitih tipova i primitiva. Recimo da imamo klasu automobila kako slijedi:

 class Car //A very simple class just to demonstrate what these definitions mean. //It pseudocode C++/Javaish, I assume strings do not need to be allocated. { private String sPrintColor; private String sModel; private String sMake; public changePaint(String newColor) { this.sPrintColor = newColor; } public Car(String model, String make, String color) //Constructor { this.sPrintColor = color; this.sModel = model; this.sMake = make; } public ~Car() //Destructor { //Because we did not create any custom types, we aren't adding more code. //Anytime your object goes out of scope / program collects garbage / etc. this guy gets called + all other related destructors. //Since we did not use anything but strings, we have nothing additional to handle. //The assumption is being made that the 3 strings will be handled by string destructor and that it is being called automatically--if this were not the case you would need to do it here. } public Car(const Car  // Copy Constructor { this.sPrintColor = other.sPrintColor; this.sModel = other.sModel; this.sMake = other.sMake; } public Car  =(const Car  // Assignment Operator { if(this !=  { this.sPrintColor = other.sPrintColor; this.sModel = other.sModel; this.sMake = other.sMake; } return *this; } } 

Duboka kopija je ako deklariramo objekt, a zatim stvorimo potpuno zasebnu kopiju objekta ... završavamo s dva objekta u 2 memorije.

 Car car1 = new Car("mustang", "ford", "red"); Car car2 = car1; //Call the copy constructor car2.changePaint("green"); //car2 is now green but car1 is still red. 

Učinimo nešto čudno. Neka se kaže da je car2 programiran ili pogrešno ili namjerno namijenjen za razmjenu stvarne memorije iz koje je vozilo napravljeno. (To je obično pogreška, a u klasama je obično spomenuto pokrivače.) Zamislite da u bilo koje vrijeme kada pitate o automobilu, stvarno odlučite pokazivač na memorijski prostor automobila1 ... koji je manje ili više tako mali postoji kopija.

 //Shallow copy example //Assume we're in C++ because it standard behavior is to shallow copy objects if you do not have a constructor written for an operation. //Now let assume I do not have any code for the assignment or copy operations like I do above...with those now gone, C++ will use the default. Car car1 = new Car("ford", "mustang", "red"); Car car2 = car1; car2.changePaint("green");//car1 is also now green delete car2; car1.changePaint("red"); 

Stoga, bez obzira na jezik koji pišete, budite vrlo oprezni što mislite kada kopirate objekte, jer većinu vremena želite dobiti duboku kopiju.

Što je konstruktor kopiranja i izjava o dodjeli kopiranja? Već sam ih koristio gore. Konstruktor kopiranja se poziva kada unosite kod kao što je Car car2 = car1; U suštini, ako deklarirate varijablu i dodijelite je u jednom retku, onda kada nazovete konstruktora kopiranja. Operator dodjele je ono što se događa kada koristite znak jednakosti - car2 = car1; , Obavijest car2 nije objavljena u istoj izjavi. Dva dijela koda koji pišete za ove operacije su vrlo vjerojatno vrlo slični. Zapravo, u tipičnom dizajnerskom uzorku postoji još jedna funkcija koju pozivate da postavite sve, nakon što ste zadovoljni početnom kopijom / dodjelom, je legalna - ako pogledate kod koji sam napisao, funkcije su gotovo identične.

Kada ih moram osobno proglasiti? Ako ne pišete kod koji treba dijeliti ili proizvesti na bilo koji način, zaista ih morate samo deklarirati kada ih trebate. Morate znati što vaš programski jezik radi, ako ga odlučite koristiti "slučajno" i to niste učinili - to jest, dobivate zadani kompilator. Primjerice, rijetko koristim konstruktore kopiranja, ali vrlo važni su nadređeni operatori dodjele. Znate li da možete redefinirati što znači dodavati, oduzimati itd.

Kako mogu spriječiti kopiranje objekata? Razumni početak je poništavanje svih načina na koje možete dodijeliti memoriju za svoj objekt pomoću privatne funkcije. Ako zaista ne želite da ih ljudi kopiraju, možete ga učiniti javno dostupnim i upozoriti programera tako što ćete izuzeti i ne kopirati objekt.

31
17 окт. odgovor je dan korisnik 1701047 17 list . 2012-10-17 19:37 '12 u 19:37 2012-10-17 19:37

Kada ih moram osobno proglasiti?

Pravilo tri države koje ako proglasite bilo koju od

  • izvod zadatka konstruktora kopiranja
  • peć za sagorijevanje otpadaka

onda morate proglasiti sve tri. Iz činjenice da potreba za korištenjem značenja operacije kopiranja gotovo uvijek slijedi iz klase koja izvodi neku vrstu upravljanja resursima, te da se gotovo uvijek podrazumijeva da

  • svako upravljanje resursima izvršeno je u jednoj operaciji kopiranja, možda je bilo potrebno izvesti kopiranje u drugom, i

  • klaster destructor će također sudjelovati u upravljanju resursima (obično ga oslobađa). Klasični upravljački resurs bio je memorija, i zato sve standardne klase knjižnice koje (na primjer, STL kontejneri koji izvode dinamičko upravljanje memorijom) sve deklariraju "velike tri": i kopiranje i destruktor.

Posljedica trećeg pravila je da prisutnost korisnički deklariranog destruktora pokazuje da jednostavna kopija člana nije prikladna za operacije kopiranja u klasi. To, pak, sugerira da ako klasa proglasi destruktor, operacije kopiranja vjerojatno ne bi trebale biti automatski generirane, jer neće učiniti pravu stvar. U vrijeme kada je usvojen C ++ 98, vrijednost ovog pravca rasuđivanja nije u potpunosti ocijenjena, stoga u C ++ 98 postojanje korisnički deklariranog destruktora nije utjecalo na spremnost kompilatora da generiraju operacije kopiranja. To je još uvijek slučaj u C ++ 11, ali samo zato što će ograničavanje uvjeta pod kojima se izvode operacije kopiranja razbiti previše zastarjeli kod.

Kako mogu spriječiti kopiranje objekata?

Izjavite konstruktor kopiranja i izraz dodjeljivanja kopiranja kao privatni specifikator pristupa.

 class MemoryBlock { public: //code here private: MemoryBlock(const MemoryBlock other) { cout<<"copy constructor"<<endl; } // Copy assignment operator. MemoryBlock operator=(const MemoryBlock other) { return *this; } }; int main() { MemoryBlock a; MemoryBlock b(a); } 

U C ++ 11 također možete izjaviti da su konstruktor kopiranja i operator dodjeljivanja uklonjeni.

 class MemoryBlock { public: MemoryBlock(const MemoryBlock other) = delete // Copy assignment operator. MemoryBlock operator=(const MemoryBlock other) =delete }; int main() { MemoryBlock a; MemoryBlock b(a); } 
21
12 янв. Odgovor daje Ajay yadav 12. siječnja 2016-01-12 12:54 '16 u 12:54 2016-01-12 12:54

Mnogi od postojećih odgovora već se odnose na konstruktora kopiranja, operatora dodjele i destruktora. Однако в post С++ 11 введение семантики перемещения может расширить это значение выше 3.

Недавно Майкл Клайс рассказал, что касается этой темы: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class

12
ответ дан wei 07 янв. '15 в 8:38 2015-01-07 08:38