C++ programlama dili: Sıfırın Kuralı veya Altı

Adanali

Active member
C++ programlama dili: Sıfırın Kuralı veya Altı


  1. C++ programlama dili: Sıfırın Kuralı veya Altı

Sıfır veya altı kuralı, modern C++’da çok önemli bir kuraldır. Bunları şu anki kitabım olan “C++ Çekirdek Yönergeleri: Modern C++ İçin En İyi Uygulamalar”da daha ayrıntılı olarak sunuyorum. Bu yazıda İngilizce kitabımdan ilgili bölümleri alıntılamak istiyorum.







Rainer Grimm, uzun yıllardır yazılım mimarı, ekip lideri ve eğitim yöneticisi olarak çalışmaktadır. C++, Python ve Haskell programlama dilleri üzerine makaleler yazmaktan hoşlanır, aynı zamanda sık sık uzmanlık konferanslarında konuşmaktan da keyif alır. Modernes C++ blogunda yoğun bir şekilde C++ tutkusundan bahsediyor.













Varsayılan olarak, gerekirse derleyici büyük altıyı oluşturabilir. Altı özel üye işlevi tanımlayabilirsiniz, ancak derleyiciden bunları sağlamasını açıkça isteyebilirsiniz. = default veya ile silin = delete.

C.20: Herhangi bir varsayılan işlemi tanımlamaktan kaçınabiliyorsanız, bunu yapın


Bu kural aynı zamanda “olarak bilinir.sıfır kuralıBu, uygun kopyala/taşı semantiğini destekleyen türleri kullanarak kendi yapıcılarınızı, kopyala/taşı yapıcıları, atama işleçlerini veya yıkıcıları yazmaktan kaçınabileceğiniz anlamına gelir.Bu, yerleşik türler bool veya double gibi normal türler için geçerlidir, ancak ayrıca Standart Şablon Kitaplığı (STL) kapsayıcıları, örneğin std::vector veya std::string.


class Named_map {
public:
// ... no default operations declared ...
private:
std::string name;
std::map<int, int> rep;
};

Named_map nm; // default construct
Named_map nm2{nm}; // copy construct


Varsayılan oluşturma ve kopya oluşturma işi, çünkü zaten tanımlanmışlardır. std::string Diğer std::map. Derleyici otomatik olarak bir sınıf için kopya oluşturucu oluşturduğunda, sınıfın tüm üyeleri ve tabanları için kopya oluşturucuyu çağırır.


C.21: Herhangi bir varsayılan işlemi tanımlarsanız veya = silerseniz, o zaman tümünü tanımlayın veya = sil


Büyük altı yakından ilişkilidir. Bu ilişki nedeniyle, o’yu tanımlamanız gerekir. =delete altı. Buna göre, bu kural denir “altı kuralı“. Bazen duyarsın”beş kuralı“, çünkü varsayılan kurucu özeldir ve bu nedenle bazen hariç tutulur.

Özel üye işlevleri arasındaki bağımlılıklar


Howard Hinnant, ACCU 2014 konferansındaki konuşmasında otomatik olarak oluşturulan üye özel özelliklerine ilişkin bir genel bakış geliştirdi.








Howard’ın tablosu derin bir açıklama gerektiriyor.

  • Her şeyden önce, kullanıcı tanımlı, bu altı özel üye işlevden birinin açıkça tanımladığınız veya derleyiciden otomatik olarak talep ettiğiniz anlamına gelir. =default. ile özel üye fonksiyonunun ortadan kaldırılması =delete ayrıca kullanıcı tarafından beyan edilmiş sayılır. Temel olarak, varsayılan oluşturucu adı gibi yalnızca adı kullandığınızda, kullanıcı tarafından beyan edilmiş sayılır.
  • Bir oluşturucu tanımladığınızda, herhangi bir varsayılan oluşturucu almazsınız. Varsayılan kurucu, bağımsız değişken olmadan çağrılabilen bir kurucudur.
  • ile bir varsayılan oluşturucu tanımladığınızda veya sildiğinizde =default veya =deletediğer altı özel üye işlevinin hiçbiri etkilenmez.
  • Bir yıkıcıyı, kopya oluşturucuyu veya kopya atama işlecini tanımlarken veya silerken =default veya =delete, herhangi bir derleyici tarafından oluşturulan taşıma oluşturucusu ve taşıma ataması oluşturucusu almazsınız. Bu, yapıyı taşıma veya atamayı taşıma gibi taşıma işlemlerinin, kopya oluşturma veya kopya atama gibi kopyalama işlemlerine geri döndüğü anlamına gelir. Bu geri dönüş otomatizmi tabloda kırmızıyla işaretlenmiştir.
  • ile tanımladığınızda veya sildiğinizde =default veya =delete bir hareket oluşturucu veya bir hareket atama operatörü, sadece kesin olanı elde edersiniz =default veya =delete yapıcıyı taşıyın veya atama operatörünü taşıyın. Sonuç olarak, kopya oluşturucu ve kopya atama operatörü şu şekilde ayarlanır: =delete. Bu nedenle, kopya oluşturma veya kopya atama gibi bir kopyalama işlemini başlatmak derleme hatasına neden olur.
Bu kurala uymadığınızda, çok mantıksız nesneler elde edersiniz. İşte yönergelerden alınan sezgisel olmayan bir örnek.


// doubleFree.cpp

#include <cstddef>

class BigArray {

public:
BigArray(std::size_t len): len_(len), data_(new int[len]) {}

~BigArray(){
delete[] data_;
}

private:
size_t len_;
int* data_;
};

int main(){

BigArray bigArray1(1000);

BigArray bigArray2(1000);

bigArray2 = bigArray1; // (1)

} // (2)


Bu programın neden tanımsız davranışı var? Varsayılan kopya atama işlemi bigArray2 = bigArray1 (1) tüm üyelerin örnek kopyaları bigArray2. Kopyalama, özellikle işaretçi verilerinin kopyalandığı ancak verilerin kopyalanmadığı anlamına gelir. Yani, için yıkıcı bigArray1 Diğer bigArray2 buna (2) denir ve çift serbest nedeniyle tanımsız davranış elde ederiz.

Örneğin sezgisel olmayan davranışı, derleyici tarafından oluşturulan kopya atama operatörünün BigArray sığ bir kopyasını yapar BigArrayancak açık uygulanan yıkıcı BigArray verilerin sahipliğini alır.

AddressSanitizer, tanımsız davranışı görünür kılar.








C.22 Varsayılan işlemleri tutarlı hale getirin


Bu kural bir önceki kuralla bağlantılıdır. Varsayılan işlemleri farklı semantiklerle uygularsanız, sınıf kullanıcılarının kafası karışabilir. Bu garip davranış, üye işlevleri kısmen uygularsanız ve kısmen bunları aracılığıyla talep ederseniz de görünebilir. =default. Derleyici tarafından oluşturulan özel üye fonksiyonların sizinkilerle aynı anlamlara sahip olduğunu varsayamazsınız.

Garip davranışa bir örnek olarak, işte sınıf Strange. Strange için bir işaretçi içerir int.


// strange.cpp

#include <iostream>

struct Strange {

Strange(): p(new int(2011)) {}

// deep copy
Strange(const Strange& a) : p(new int(*a.p)) {} // (1)

// shallow copy
// equivalent to Strange& operator
// = (const Strange&) = default;
Strange& operator = (const Strange& a) { // (2)
p = a.p;
return *this;
}

int* p;

};

int main() {

std::cout << 'n';

std::cout << "Deep copy" << 'n';

Strange s1;
Strange s2(s1); // (3)

std::cout << "s1.p: " << s1.p << "; *s1.p: " << *s1.p << 'n';
std::cout << "s2.p: " << s2.p << "; *s2.p: " << *s2.p << 'n';

std::cout << "*s2.p = 2017" << 'n';
*s2.p = 2017; // (4)

std::cout << "s1.p: " << s1.p << "; *s1.p: " << *s1.p << 'n';
std::cout << "s2.p: " << s2.p << "; *s2.p: " << *s2.p << 'n';

std::cout << 'n';

std::cout << "Shallow copy" << 'n';

Strange s3;
s3 = s1; // (5)

std::cout << "s1.p: " << s1.p << "; *s1.p: " << *s1.p << 'n';
std::cout << "s3.p: " << s3.p << "; *s3.p: " << *s3.p << 'n';


std::cout << "*s3.p = 2017" << 'n';
*s3.p = 2017; // (6)

std::cout << "s1.p: " << s1.p << "; *s1.p: " << *s1.p << 'n';
std::cout << "s3.p: " << s3.p << "; *s3.p: " << *s3.p << 'n';

std::cout << 'n';

std::cout << "delete s1.p" << 'n'; // (7)
delete s1.p;

std::cout << "s2.p: " << s2.p << "; *s2.p: "
<< *s2.p << 'n'; // (8)
std::cout << "s3.p: " << s3.p << "; *s3.p: " << *s3.p << 'n';

std::cout << 'n';

}


sınıf Strange bir kopya oluşturucuya (1) ve bir kopya atama operatörüne (2) sahiptir. Kopya oluşturucu derin kopyalama uygular ve atama operatörü yüzeysel kopyalama uygular. Bu arada, derleyici tarafından oluşturulan kopya oluşturucu veya kopya atama operatörü de yüzeysel kopyalama uygular. Çoğu zaman, türleriniz için derin kopya semantiği (değer semantiği) istersiniz, ancak muhtemelen bu iki ilişkili işlem için farklı semantiklere sahip olmak istemezsiniz. Aradaki fark, derin kopya semantiğinin iki yeni ayrı arşiv oluşturmasıdır. p(new int(*a.p)) sığ kopyalama semantiği ise sadece işaretçiyi kopyalar p = a.p. ile oynayalım Strange türleri. Aşağıdaki şekil programın çıktısını göstermektedir.








(3) oluşturmak için kopya oluşturucuyu kullanın s2. İşaretçi adreslerini görüntüleme ve işaretçi değerini değiştirme s2.p (4) kanıtlıyor s1 Diğer s2 onlar iki ayrı nesnedir. durum böyle değil s1 Diğer s3. (5)’teki kopya atama işlemi sığ bir kopya gerçekleştirir. Sonuç, işaretçiyi değiştirerek s3.p (6) işaretçiyi de etkiler s1.p çünkü her iki işaretçi de aynı değeri ifade eder.

İşaretçiyi silersem eğlence başlar s1.p (7). Derin kopya sayesinde kötü bir şey olmuyor s2.p, ama değeri s3.p geçersiz bir işaretçi olur. Daha spesifik olmak gerekirse: geçersiz bir işaretçiyi olduğu gibi kaldırın *s3.p (8) tanımsız davranıştır.

Sıradaki ne?


Normal tür kavramı, Standart Şablon Kitaplığı’na (STL) sıkı sıkıya bağlıdır. Fikir, STL’nin yaratıcısı Alexander Stephanov’a kadar uzanıyor. Bir sonraki yazımda normal tipler hakkında daha fazla yazacağım.


(rm)



ana sayfaya
 
Üst