Üç Kuralı nedir?
Nedirbir nesneyi kopyalamademek? Nelerdirkurucu kopyalayınvekopya atama operatör? Ne zaman onlara kendimi tanıtmaya gerek var mı? Nasıl kopyalanmasını benim nesneleri engelleyebilir miyim?
CEVAP
Giriş
C kullanıcı tanımlı değişkenleri türleri davranırdeğer semantiği. Bu nesneleri dolaylı olarak çeşitli bağlamlarda kopyalanır anlamına gelir ve aslında "anlamına gelir. bir nesne kopyalama ne anlamalıyız
Bize basit bir örnek verebiliriz:
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?
}
(name(name), age(age)
parçası şaşıran,
bu*. *45) denir.
Özel üye işlevleri
person
bir nesneyi kopyalamak için ne anlama geliyor?
main
Bu fonksiyon iki farklı kopyalama senaryoları gösterir.
Başlatma person b(a);
tarafından yapılırkurucu kopyalayın.
İşini temiz bir nesne, varolan bir nesnenin durumuna bağlı olarak oluşturmaktır.
12 ** atama ile gerçekleştirilirkopya atama operatör.
İşini genellikle biraz daha karışık.
hedef nesne zaten bazı geçerli durumda olduğu için bu çözülmesi gereken.
Ne kopya kurucu ne de atama operatörü (veya yıkıcı) kendimizi ilan ettik,beri bu bizim için örtülü olarak tanımlanır. Standart alıntı:
[...] Yapıcı ve kopya atama operatör kopya, [...] ve yıkıcı özel üye işlevleri. [Not:Bu uygulama örtülü bu üye fonksiyonlar ilan edecek program açık bir şekilde beyan etmez bazı sınıf türleri için. Bu uygulama örtülü olarak eğer kullanılırsa onları tanımlamak olacaktır. [...]not end] [n3126.pdf bölüm 12 §1]
Varsayılan olarak, bir nesne kopyalama üyeleri kopyalama anlamına gelir:
Örtülü olarak tanımlanan kopya sendika üyesi olmayan X bir sınıf için yapıcı subobjects bu memberwise bir kopyasını yapar. [n3126.pdf bölüm 12.8 §16]
Sendikasız sınıf X için örtülü olarak tanımlanan kopya atama operatör memberwise kopya atama yapar subobjects. [n3126.pdf bölüm 12.8 §30]
Örtük tanımlar
Örtülü olarak tanımlı person
özel üye işlevleri şöyle:
// 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()
{
}
Memberwise kopyalama bu durumda istediğimiz şey tam olarak bu:
name
age
müstakil bağımsız person
bir nesne bulduk .. kopyalanır.
Örtülü olarak tanımlanan yıkıcı her zaman boştur.
Bu oluşturucu içinde herhangi bir kaynak elde etmedik bu yana da bu durumda gayet iyi.
Üyelerin yıkıcılar person
yıkıcı tamamlandıktan sonra örtülü olarak da bilinir
Yıkıcı vücut yürüttükten sonra ve herhangi bir otomatik nesneleri bünyesinde ayrılan yok, sınıf için X X doğrudan yıkıcılar [...] bir yıkıcı çağrıları üyeleri [n3126.12.4 §6] pdf
Kaynakları yönetme
Bu özel üye işlevleri açıkça ilan etmeli miyiz? Ne zaman bizim sınıfyöneten bir kaynakyani sınıfın bir nesnesi olduğundasorumlubu kaynak için. Bu genellikle kaynak olduğu anlamına gelirsatın aldıkurucu (ya da kurucu) geçtiyayınladıyıkıcı.
Bizi zamanda geriye gitmek pre-standart C için izin verin .
std::string
ve programcılar işaretçiler aşık gibi böyle bir şey yok.
person
sınıfı bu gibi görünebilir:
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;
}
};
Bugün bile, insanlar hala bu tarzda sınıflar yazmak ve belaya:
"Bir vektör içine bir kişi ittim ve şimdi çılgın bir bellek hatası alıyorum!"
Varsayılan olarak, bir nesne kopyalama üyeleri kopyalama anlamına geldiğini unutmayın
ama kopyalama name
üye sadece kopya bir işaretçideğilişaret, karakter dizisi!
Bu birkaç tatsız etkileri vardır:
a
ile değiştirirb
üzerinden görülebilir.b
,a.name
yok edilince sarkan bir işaretçi.- Eğer
a
yok ise, sarkan işaretçi silme undefined behavior verir. - Atama
name
atama önce işaret,ne dikkate almaz beri er ya da geç her yerde bellek sızıntısı alacak.
Açık tanımlar
Memberwise kopyalamak istediğiniz efekti olmadığı için, kopya yapıcı ve kopya atama operatör açıkça karakter dizisi derin kopyalarını yapmak için tanımlamanız gerekir:
// 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 != &that)
{
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;
}
Not başlatma ve atama arasındaki fark:
name
bellek sızıntıları önlemek için atamadan önce eski devleti yıkmak zorundayız.
Ayrıca, form x = x
kendi kendine atama karşı korumak zorundayız.
Olmadan kontrol delete[] name
dizi içeren silmek istiyorsunuzkaynakdize,
yazarken x = x
, this->name
that.name
her ikisi de aynı işaretçi içerir çünkü.
Özel güvenlik
Ne yazık ki, bu çözüm, eğer new char[...]
bir istisna bellek tükenmesi nedeniyle atar başarısız olur.
Olası bir çözüm, yerel bir değişken tanıtmak ve tabloları yeniden düzenlemek için
// 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;
}
Bu da açık bir kontrol olmadan, kendi kendine atama ilgilenir. Bu sorun için daha sağlam bir çözüm copy-and-swap idiom. ama özel güvenlik detaylarına burada girmeyeceğim. Ben sadece şu noktaya yapmak için özel durumlar söz:Kaynakları yönetmek sınıfları yazmak çok zor.
Noncopyable kaynakları
Bazı kaynaklar ya da kopyalanmış olmamalı, kolları ya da uyumu dosyası gibi.
Bu durumda, sadece bir tanım vermeden private
kopya Kurucu ve kopya atama operatör ilan:
private:
person(const person& that);
person& operator=(const person& that);
Alternatif olarak, boost::noncopyable
miras ya da silinmiş (C 0) bildirebilirsiniz:
person(const person& that) = delete;
person& operator=(const person& that) = delete;
Üç kuralı
Bazen bir kaynak yöneten bir sınıf uygulamak gerekir. (Asla tek bir sınıfta birden fazla kaynakları yönetmek bu sadece ağrıya yol açabilir.) Bu durumda, unutmayınüç kuralı:
Eğer ya da açıkça yıkıcı ilan etmek gerekiyorsa, ya atama operatörü kopyasını kendinize bir kopya kurucu, muhtemelen açıkça üçü de bildirmeniz gerekir.
(Ne yazık ki, bu "" C standart veya herhangi bir derleyici farkındayım.) tarafından zorlanmaz kural
Tavsiye
Çoğu zaman, bir kaynak kendini yönetmek gerekmez
çünkü std::string
gibi varolan bir sınıf zaten bunu sizin için yapar.
Sadece basit kodu std::string
bir üyesi kullanarak karşılaştırın
karmaşık ve hata eğilimli bir alternatif kullanarak char*
ve ikna edilmesi gerekir.
Ham işaretçi üyeleri uzak kaldığın sürece, üç kuralı kendi kodunuzu endişe pek mümkün değildir.
Sıkı yumuşatma kuralı nedir?...
Değişken ve işlev adları için Python a...
Bu CSS kuralı Krom gri olduğunda yani&...
Html/css kimlikleri ve sınıfları için ...
Boolean bir alan için, alıcı/ayarlayıc...