Üçün üstünlüyü nədir?

  • Bir obyektin nüsxəsi nə deməkdir?
  • Bir surəti qurucusu və surəti tapşırıq bəyanatı nədir?
  • Onları özümə necə bəyan etməliyəm?
  • Nesnelerimin kopyalanmasını nasıl önleyebilirim?
1911
13 нояб. 13 Noyabrda fredoverflow tərəfindən təyin olundu 2010-11-13 16:27 '10 at 16:27 2010-11-13 16:27
@ 8 cavab

Giriş

Semantik dəyərləri olan C ++ prosesləri xüsusi növ dəyişənlər. Bu, obyektlərin müxtəlif kontekstlərdə örtülü şəkildə köçürülməsi deməkdir və biz "obyektin surətini" həqiqətən anlamadığını başa düşmək lazımdır.

Sadə nümunəni nəzərdən keçirin:

 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? } 

(Əgər name(name), age(age) ilə təəccüblənsəniz, bu , üzv təşəbbüskarların siyahısıdır .)

Xüsusi üzv funksiyaları

Biri kopyalamaq nə deməkdir? main funksiya iki müxtəlif surəti skriptini göstərir. Initialization person b(a); surəti qurucusu tərəfindən icra edilir. Onun vəzifəsi mövcud bir obyektin vəziyyətinə əsaslanan yeni bir obyekt qurmaqdır. Tapşırıq b = a surəti tapşırıq operatoru tərəfindən həyata keçirilir. Onun işi adətən bir az daha çətindir, çünki hədəf obyekti həll edilməli olan bəzi hallarda məqbul vəziyyətdədir.

Nüsxə konstruktorunu və ya tapşırıq operatorunu (və ya dağıdıcıyı) elan etmədiyimiz üçün onlar bizim üçün tam olaraq müəyyən edilirlər. Standartdan sitat:

Kopyalama konstruktoru [...] və surəti tapşırıq operatoru, [...] və dağıdıcı xüsusi üzv funksiyalarıdır. [Qeyd: Proqram, bu proqram funksiyalarını aydın şəkildə bəyan etmədikdə, bu üzv funksiyalarını müəyyən sinif növləri üçün açıq şəkildə elan edir. Tətbiq tətbiq olunarsa, onları tam olaraq müəyyənləşdirəcəkdir. [...] sonuncu qeyd] [n3126.pdf bölmə 12 §1]

Varsayılan olaraq, bir obyektin kopyalanması, elementlərini kopyalamak deməkdir:

Qeyri-vahid sinif X üçün gizli təsvir edilmiş bir surəti konstruktoru onun sub-obyektlərinin mərhələli surətini yerinə yetirir. [n3126.pdf bölmə 12.8 §16]

Qeyri-vahid sinif X üçün gizli təyin edilmiş surəti tapşırıq operatoru alt obyektlərin bir surətinin mərhələli tapşırığını yerinə yetirir. [n3126.pdf bölmə 12.8 §30]

Həqiqi təriflər

Bir person üçün örtülü olaraq təyin olunan xüsusi üzv funksiyaları aşağıdakılardır:

 // 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() { } 

Bu vəziyyətdə istədiyimizi kopyalamaq istəyirik: nameage kopyalanır, buna görə müstəqil, müstəqil bir person obyekti oluruq. Tam olaraq müəyyən edilmiş yıkıcı hər zaman boşdur. Bu da, bu vəziyyətdə böyükdür, çünki biz konstruktorda heç bir resurs almadık. İstifadəçi yıxan şəxslər yıxıcıyı tamamladıqdan sonra ziddiyyətlə çağırılır:

Dağıdıcı orqanını yerinə yetirdikdən və bədənində ayrılan hər hansı bir avtomatik obyektin məhv edildikdən sonra, X sinfi dağıdıcı X düz üzvləri üçün dağıdıcılara səbəb olur [n3126.pdf 12.4 §6]

Resursların idarə edilməsi

Beləliklə, biz bu xüsusi üzv funksiyasını açıq şəkildə elan etməlisiz? Sınıfımız bir qaynağı idarə edərkən, yəni bir sinfi obyekti bu qaynaq üçün məsuliyyət daşıyır. Bu, ümumiyyətlə, mənbənin konstruktorda əldə olunduğunu (ya da konstruktorun köçürülməsini) və dağıdıcıda sərbəst buraxılmasını nəzərdə tutur.

Gəlin ön standart C ++-a geri qayıdırıq. std::string kimi bir şey yox idi və proqramçılar göstəricilərə aşiq olmuşdular. person sinfi bu şəkildə görünə bilər:

 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; } }; 

Bu gün belə insanlar hələ də bu tərzdə dərslər yazırlar və narahat olurlar: "Bir insanı bir vektöre çevirdim və indi səhv yaddaş səhvləri aldım!" Bir obyektin surətini çıxarmaq onun elementlərini kopyalamaq deməkdir, ancaq name üzvünün kopyalanması, işarəçiyə göstərilən simvollar dizisini deyil, sadəcə kopyalayın! Bunun bir çox xoşagəlməz təsirləri var:

  • a dəyişiklik vasitəsilə b .
  • b məhv edildikdən sonra, a.name göstəricisidir.
  • Bir məhv edildikdə, qırılan pointer aradan qaldırılması müəyyənləşdirilməyən davranış verir.
  • Tapşırıq təyinatın əvvəlində göstərilən name nəzərə almadığından, tezliklə hər yerdə yaddaş sızıntısını alacaqsınız.

Açıq təriflər

Kopyalamanız istənilən effektə malik olmadığı üçün, xarakterli ardıcıllığın dərin surətlərini yaratmaq üçün surəti konstruktorunu və surəti tapşırıq operatorunu dəqiq şəkildə müəyyənləşdirməliyik:

 // 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; } 

Başlatma və təyinat arasında fərqə diqqət yetirin: yaddaşın sızması qarşısını almaq üçün name atmadan əvvəl köhnə dövləti aşağı çəkin. Bundan əlavə, biz x = x formasını x = x qoruymalıyıq. Bu çek olmadan delete[] name sətri mənbə that.name ehtiva edən that.name , çünki x = x yazdığınızda, həm this->namethat.name eyni göstəricini ehtiva edir.

İstisna Təhlükəsizliyi

Təəssüf ki, bu new char[...] çözüm new char[...] yaddaşın tükənməsi səbəbindən bir istisna atırsa bu həll uğursuz olacaq. Olası bir həll yerli dəyişən təqdim etmək və operatorların sırasını dəyişdirməkdir:

 // 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; } 

O, həmçinin dəqiq yoxlama olmadan özünü təyin edir. Bu problemin daha etibarlı bir həlli , çıxarmaq və dəyişdirmə deyimidir , amma təhlükəsizlik istisnalarının detallarına daxil olmayacağam. Mən yalnız aşağıdakıları istisna edirəm: Resursları idarə edən dərs yazıları çətindir.

Dəstəksiz qaynaqlar

Bəzi qaynaqlar kopyalanamaz və ya olmamalıdır, məsələn fayl təsvirçiləri və ya mutekslər. Bu halda, bir təsviri göstərmədən, surəti qurucusunu və nüsxə təyinat operatorunu private olaraq elan edin:

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

Alternativ olaraq, boost::noncopyable miras boost::noncopyable və ya silinmiş olaraq boost::noncopyable (C ++ 0x):

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

Üç hökm

Bəzən bir resurs idarə edən bir sinif tətbiq etməlisiniz. (Heç vaxt eyni sinifdə birdən çox qaynaq idarə etmə, yalnız ağrıya səbəb olacaq). Bu halda, üçün üstünlüyünü yadda saxlayın:

Dağıdıcıyı açıq şəkildə elan etməlisinizsə, konstruktor və ya surəti tapşırıq bəyanatını özünüzü kopyalayın, ehtimal ki, üçü açıq şəkildə bəyan etməlisiniz.

(Təəssüf ki, bu "qayda" C ++ standartı və ya bildiyim kompilyator tərəfindən tətbiq edilmir.)

Şura

Çox hallarda, resursları özünüz idarə etməli deyilsiniz, çünki std::string kimi mövcud bir sinif artıq bunu sizin üçün edir. std::string üzvüylə sadə kodu müqayisə edərək char* istifadə edərək səhv və səhv meylli bir alternativə əməl edin və əmin olun. Çörək göstəricilərindən uzaqlaşdığınız müddətdə, üçün üstünlüyü öz kodunuza tətbiq olunur.

1561
13 нояб. cavab noyabr 13-də fredoverflow tərəfindən verilir 2010-11-13 16:27 '10 at 16:27 2010-11-13 16:27

Üç qayda odur ki, C ++ üçün başparmak qaydasıdır

Sınıfınızın hər hansı birinə ehtiyacı var

  • bir surəti qurucusu ,
  • tapşırıq operatoru,
  • və ya dağıdıcı ,

aydın müəyyən edildikdə, ehtimal ki, hamısı üçü tələb olunacaq.

Bunun səbəbləri bunların üçü ümumiyyətlə resursları idarə etmək üçün istifadə olunur və sinfi resursları idarə edərsə, adətən kopyalamayı və sərbəst idarə etmək lazımdır.

border=0

Sınıfınızın yönettiği kaynakları kopyalamak üçün yaxşı bir semantik bulunmuyorsa, kopyalama yapıcısını ve atama operatörünü özel olarak ( belirlemeyerek ) kopyalamayı private .

(Qeyd edək ki, C ++ standartının (C ++ 11) yeni versiyası C ++-da hərəkət semantiklərini əlavə edir və bu, ehtimal ki, üçü dəyişəcəkdir. Bununla yanaşı C + Üçüncü Qaydada +11 bölmə.)

467
13 нояб. Cavab 13 noyabr tarixində sbi tərəfindən verilmişdir. 2010-11-13 17:22 '10 at 17:22 2010-11-13 17:22

Böyük üç qanunu yuxarıda göstərildiyi kimidir.

Çözdüyü problemin sadə ingilis dilində asan bir nümunəsi:

Xüsusi dağıdıcı

Yaddaşınızı konstruktorunuza ayırdınız və onu aradan qaldırmaq üçün yıxıcı yazmalısınız. Əks halda, bir yaddaş sızması meydana gələcək.

Bunun bir iş olduğunu düşünə bilərsiniz.

Əgər surət obyektinizdən çıxarılsa problem olacaqdır, onda surət orijinal obyekt kimi eyni yaddaşa işarə edir.

Onlardan biri yıxıcının yaddaşını ləğv etməzdən sonra, digərinin yalnış yaddaşa bir göstəricisi var (bu sanki göstərici deyilir), onu istifadə etməyə çalışır, hər şey tüylü görünür.

Nəticə olaraq, bir surəti qurucusunu yazırsınız ki, yeni obyektləri yaddaşın öz fraqmentlərini məhv edər.

Atama operatoru və surəti qurucusu

Qurumunuzdakı yaddaşınızı sinif üzvinizə bir göstərici ayırdınız. Bu sinifin bir obyektini kopyaladığınızda, atanmış tapşırıq bəyanatı və surəti qurucusu bu göstərici elementinin dəyərini yeni obyektə kopyalayacaq.

Yəni, yeni obyekt və köhnə obyekt yaddaşın eyni hissəsinə işarə edir, buna görə bir obyektdə dəyişdiyiniz zaman başqa obyekt objerct üçün dəyişdiriləcəkdir. Bir obyekt bu yaddaşını ləğv etsə, başqa bir istifadə etməyə çalışacaq - eek.

Bu problemi həll etmək üçün surəti konstruktorunun və tapşırıq operatorunun öz versiyasını yazırsınız. Sürümleriniz yeni obyektlərə ayrı yaddaş ayırır və ünvanını deyil, ilk göstərici tərəfindən göstərilən dəyərləri kopyalayın.

143
14 мая '12 в 17:22 2012-05-14 17:22 14 Mayda Stefana cavab verdi , '12 'də saat 05:22' da 2012-05-14 17:22

Əsasən, bir destruktorunuz varsa (default destructor deyil), bu, müəyyən etdiyiniz sinifdə bir yaddaş ayırmağına malikdir. Bir sinif, bəzi müştəri kodu və ya siz tərəfindən xaricində istifadə edilir deyək.

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

MyClass yalnız bir sıra ibtidai tipli üzvlərə malikdirsə, atanmış tapşırıq bəyanatı yerinə yetiriləcək, amma bəzi göstərici elementləri və tapşırıqları olmayan obyektlər varsa, nəticə gözlənilməz olacaqdır. Buna görə də deyə bilərik ki, əgər sinif dağıdıcıda bir şey aradan qaldırılsaydı, dərin bir surət operatoruna ehtiyacımız ola bilər, buna görə biz surəti konstruktor və təyinat operatoru təqdim etməmiz deməkdir.

39
31 дек. cavab verildi fatma.ekici 31 dekabr. 2012-12-31 22:29 '13 at 22:29 2012-12-31 22:29

Bir obyektin nüsxəsi nə deməkdir? Obyektlərin surətini çıxarmaq üçün bir neçə yol var - ən çox ehtimal etdiyiniz iki növü haqqında məlumat verin: dərin surət və dərin bir surət.

Bir obyekt yönümlü dildə olduğumuzdan (və ya ən azı onu düşünsək), məsələn, xüsusi bir yaddaş parçası olduğunuzu söyləyə bilərik. Bu bir OO dili olduğundan, asanlıqla ayırdığımız yaddaş parçaları ilə əlaqələndirə bilərik, çünki onlar adətən ilk növbəli dəyişənlər (ints, chars, baytlar) və ya öz növlərimiz və primitivlərimizdən ibarət olan müəyyənləşdirilmiş dərslərdir. Beləliklə, avtomobilin bir sinif var, deyək:

 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; } } 

Dərin bir surət bir obyektin elan edildiyi və obyektin tamamilə ayrı bir surətini yaratdığımızdan ibarətdir ... 2 tam yaddaş dəstində iki obyekt ilə bitiririk.

 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. 

İndi qəribə bir şey edək. Avtomobil2 ya yanlış və ya qəsdən nəzərdə tutulmuşdur ki, avtomobil1dən olan faktiki yaddaşı dəyişdirməyi nəzərdə tutur. (Bu, adətən, səhvdir və siniflərdə adətən qeyd olunan battaniyadır) Təsəvvür edin ki, avtomobil2 haqqında soruşduğunuz zaman, həqiqətən, avtomobilin yaddaş yerinə göstəricini təyin edirsiniz ... bu çox az bir surət var.

 //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"); 

Buna görə də, yazdığınız dildən asılı olmayaraq, obyektlərin surətini çıxararkən nə demək lazım olduğuna çox diqqətli olun, çünki çox vaxt dərin bir surət əldə etmək istəyir.

Bir surəti qurucusu və surəti tapşırıq bəyanatı nədir? Onları artıq istifadə etmişəm. Car car2 = car1; kimi bir kod daxil edərkən surəti Car car2 = car1; Əslində, bir dəyişən elan edir və bir satırda atarsanız, surəti qurucusunu çağırdığınızda. car2 = car1; operatoru bərabər işarəni istifadə edərkən nə olur - car2 = car1; . Bildirişdə car2 eyni bəyanatda deyil. Bu əməliyyatlar üçün yazdığınız iki ədəd kodu çox güman ki çox oxşardır. Əslində, tipik bir dizayn desənizdə, hər şeyi təyin etmək üçün çağırdığınız bir funksiya var, birincil surət / tapşırıqdan razı qaldıqdan sonra, hüquqi olduğunuzda - yazdığım koda baxarsanız, funksiyalar demək olar ki, eynidır.

Onları özümə necə bəyan etməliyəm? Əgər hər hansı bir şəkildə paylaşılmalı və ya istehsal edilməli olan kod yazmırsınızsa, həqiqətən, onlara ehtiyac duyduğunuz zaman onları bəyan etmək lazımdır. Proqramlaşdırma dilinizin nə olduğunu bilmək lazımdır, əgər "təsadüfən" istifadə etməyə qərar verərsinizsə və bunu etməmisinizsə - default derleyiciyi alırsınız. Məsələn, nadir hallarda kopyalama qurucularını istifadə edirəm, amma tapşırıq operatorlarını ləğv etmək çox yaygındır. Əlavə etmək, çıxarmağın və s.

Nesnelerimin kopyalanmasını nasıl önleyebilirim? Həssas bir başlanğıc, özəl funksiyanı istifadə edərək, obyektiniz üçün yaddaş ayırdığınız bütün yolları qüvvədən keçirməkdir. İnsanlar onları kopyalamağını həqiqətən istəmirsinizsə, ictimaiyyətə çatdırmaq və proqramçıya bir istisna ataraq və obyektin surətini çıxarmaqla xəbərdar edə bilərsiniz.

31
17 окт. Cavab 170104717 oktyabr ayına verilir . 2012-10-17 19:37 '12 at 7:37 pm 2012-10-17 19:37

Onları özümə necə bəyan etməliyəm?

Hər hansı bir dövləti elan etdiyiniz üç dövlətin qayda

  • kopyalayıcı konstruktor təyin bəyanatı
  • dağıdıcı

onda üçü elan etməlisiniz. Bir surət əməliyyatının mənasını istifadə ehtiyacının demək olar ki, həmişə hər cür resurs idarə edən bir sinifdən gəlir və demək olar ki, həmişə

  • bir surət əməliyyatında istənilən resursların idarə edilməsi həyata keçirilmişdirsə, surəti əməliyyatları başqa bir şəkildə yerinə yetirmək üçün lazım ola bilər

  • sinfi dağıdıcı da resurs idarəçiliyinə qatılacaq (adətən onu azad edir). Klassik idarəetmə resursu yaddaş idi və buna görə də bütün standart kitabxana dərsləri (məsələn, dinamik yaddaş idarə edən STL konteynerləri) "böyük üçü" elan edir: həm kopiya əməliyyatları, həm də dağıdıcı.

Qaydanın üçü nəticəsidir ki, istifadəçi tərəfindən elan edilmiş dağıdıcı varlığı, üzvün sadə bir nüsxəsi sinifdə surət əməliyyatları üçün uyğun deyildir. Bu, öz növbəsində, bir sinfi yıkıcı elan edərsə, kopya əməliyyatları, həqiqətən, doğru bir şey etməyəcəkləri üçün, avtomatik olaraq yaradılmamalıdır. C ++ 98 qəbul edildikdə, bu xəttin xəttinin dəyəri tam olaraq qiymətləndirilməyib, buna görə də C ++ 98-də istifadəçi elan edilmiş yıxıcıların varlığı kompüterlərin surəti əməliyyatları yaratmağı hazırlamağa təsir etməmişdir. Bu hələ də C ++ 11-də işləyir, ancaq kopiya əməliyyatları yerinə yetirilən şərtləri məhdudlaşdırmaq yalnız çox köhnəlmiş kodu pozacaq.

Nesnelerimin kopyalanmasını nasıl önleyebilirim?

Kopyalama konstruktorunu və surəti tapşırıq bəyanatını xüsusi giriş spesifikatoru kimi bildirin.

 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); } 

C ++ 11 də, surəti konstruktorunun və tapşırıq operatorunun çıxarıldığını da bildirə bilərsiniz.

 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 янв. Cavab 12 yanvar Ajay yadav tərəfindən verilir 2016-01-12 12:54 '16 at 12:54 2016-01-12 12:54

Mövcud cavabların çoxu artıq surəti qurucusu, tapşırıq operatoru və yıxıcıya aiddir. Lakin post C ++ 11-də, hərəkət semantikasının tətbiqi bu dəyəri 3-dən yuxarı uzada bilər.

Bu yaxınlarda Michael Clyce bu mövzuda danışdı: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class

12
07 янв. Cavab verilir 07 yanvar. 2015-01-07 08:38 '15 at 8:38 AM 2015-01-07 08:38

Правило из трех в С++ является основополагающим принципом проектирования и разработки трех требований, которые, если есть четкое определение в одной из следующих функций-членов, затем программист должен определить две другие функции-члены вместе. А именно, необходимы следующие три функции-члена: деструктор, конструктор копирования, оператор присваивания копии.

Конструктор копирования в С++ является специальным конструктором. Он используется для создания нового объекта, который является новым объектом, эквивалентным копии существующего объекта.

Оператор присваивания копий - это специальный оператор присваивания, который обычно используется для указания существующего объекта другим лицам того же типа.

Быстрые примеры:

 // default constructor My_Class a; // copy constructor My_Class b(a); // copy constructor My_Class c = a; // copy assignment operator b = a; 
8
ответ дан Marcus Thornton 12 авг. '14 в 7:27 2014-08-12 07:27

Другие вопросы по меткам или Задайте вопрос