C ++ derleyicisinin bir sınıf için bir kopya yapıcısı oluşturduğunu biliyorum. Hangi durumda kullanıcı tanımlı bir kopya oluşturucu yazmamız gerekir? Bazı örnekler verebilir misiniz?
C ++ derleyicisinin bir sınıf için bir kopya yapıcısı oluşturduğunu biliyorum. Hangi durumda kullanıcı tanımlı bir kopya oluşturucu yazmamız gerekir? Bazı örnekler verebilir misiniz?
Yanıtlar:
Derleyici tarafından oluşturulan kopya oluşturucu, üye bazında kopyalama yapar. Bazen bu yeterli değildir. Örneğin:
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;
}
bu durumda üyenin üye bazında kopyalanması stored
tamponu çoğaltmayacaktır (sadece işaretçi kopyalanacaktır), bu nedenle yok edilecek ilk kopya tamponu paylaşarak delete[]
başarılı bir şekilde çağıracak ve ikincisi tanımlanmamış davranışla çalışacaktır. Derin kopyalama kopyalama yapıcısına (ve ayrıca atama operatörüne) ihtiyacınız var.
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;
}
delete stored[];
ve bence olması gerektiğine inanıyorumdelete [] stored;
std::string
. Genel fikir, yalnızca kaynakları yöneten yardımcı sınıfların Büyük Üçü aşırı yüklemesi gerektiğidir ve diğer tüm sınıfların, Büyük Üçten herhangi birini tanımlama ihtiyacını ortadan kaldırarak yalnızca bu yardımcı sınıfları kullanması gerektiğidir.
Kuralına Rule of Five
atıfta bulunulmaması beni biraz sinirlendirdi .
Bu kural çok basit:
Beş Kuralı :
Destructor, Copy Constructor, Copy Assignment Operator, Move Constructor veya Move Assignment Operator'dan birini yazdığınızda, muhtemelen diğer dördünü de yazmanız gerekir.
Ancak, istisnai durum korumalı kod yazma ihtiyacından kaynaklanan, izlemeniz gereken daha genel bir kılavuz var:
Her kaynak özel bir nesne tarafından yönetilmelidir
Buradaki @sharptooth
kod hala (çoğunlukla) iyidir, ancak sınıfına ikinci bir nitelik ekleseydi bu olmazdı. Aşağıdaki sınıfı düşünün:
class Erroneous
{
public:
Erroneous();
// ... others
private:
Foo* mFoo;
Bar* mBar;
};
Erroneous::Erroneous(): mFoo(new Foo()), mBar(new Bar()) {}
new Bar
Atarsa ne olur ? İle gösterilen nesneyi nasıl silersiniz mFoo
? Çözümler var (işlev seviyesi dene / yakala ...), sadece ölçeklenmiyorlar.
Durumla başa çıkmanın doğru yolu, ham işaretçiler yerine uygun sınıfları kullanmaktır.
class Righteous
{
public:
private:
std::unique_ptr<Foo> mFoo;
std::unique_ptr<Bar> mBar;
};
Aynı yapıcı uygulamasıyla (veya aslında kullanarak make_unique
), artık ücretsiz olarak istisna güvenliğine sahibim !!! Heyecanlı değil mi? Ve en iyisi, artık uygun bir yıkıcı için endişelenmeme gerek yok! Ben kendi yazmaya gerek yok Copy Constructor
ve Assignment Operator
çünkü olsa unique_ptr
bu işlemleri tanımlamaz ... ama mesele burada değil;)
Ve bu nedenle, sharptooth
s sınıfı yeniden ziyaret edildi:
class Class
{
public:
Class(char const* str): mData(str) {}
private:
std::string mData;
};
Seni bilmiyorum ama benimkini daha kolay buluyorum;)
Pratiğimden hatırlayabilirim ve kopya oluşturucuyu açıkça bildirmek / tanımlamakla uğraşmak zorunda olduğum aşağıdaki durumları düşünebilirim. Vakaları iki kategoriye ayırdım
Bu bölüme, bu türü kullanan programların doğru çalışması için kopya oluşturucunun bildirilmesi / tanımlanmasının gerekli olduğu durumları yerleştiriyorum.
Bu bölümü okuduktan sonra, derleyicinin kopya oluşturucuyu kendi başına oluşturmasına izin vermenin birkaç tuzağını öğreneceksiniz. Bu nedenle, sanıldığı gibi onun belirtildiği cevap , yeni bir sınıf için copyability kapatın ve her zaman güvenlidir kasten gerçekten gerektiğinde daha sonra etkinleştirmek.
Özel bir kopya-yapıcı bildirin ve bunun için bir uygulama sağlamayın (böylece, bu türdeki nesneler sınıfın kendi kapsamında veya arkadaşları tarafından kopyalanmış olsa bile derleme bağlama aşamasında başarısız olur).
Copy-constructor'ı =delete
sonunda ile bildirin .
Bu en iyi anlaşılan durumdur ve aslında diğer cevaplarda bahsedilen tek durumdur. shaprtooth etti kaplı oldukça iyi. Yalnızca nesneye özel olarak sahip olunması gereken derin kopyalama kaynaklarının, dinamik olarak ayrılan belleğin yalnızca bir türü olduğu her tür kaynak için geçerli olabileceğini eklemek istiyorum. Gerekirse, bir nesneyi derinlemesine kopyalamak da gerekli olabilir
Tüm nesnelerin - nasıl inşa edildikleri önemli değil - bir şekilde kaydedilmesi ZORUNLU olduğu bir sınıf düşünün. Bazı örnekler:
En basit örnek: şu anda var olan nesnelerin toplam sayısını korumak. Nesne kaydı, statik sayacı artırmakla ilgilidir.
Daha karmaşık bir örnek, bu türdeki tüm mevcut nesnelere yapılan referansların depolandığı (böylece bildirimlerin hepsine iletilebilmesi için) tek bir kayıt defterine sahip olmaktır.
Referans sayılan akıllı işaretçiler bu kategoride yalnızca özel bir durum olarak kabul edilebilir: yeni işaretçi, kendisini genel bir kayıt defteri yerine paylaşılan kaynakla "kaydeder".
Böyle bir kendi kendine kayıt işlemi, türün HERHANGİ bir kurucusu tarafından gerçekleştirilmelidir ve kopya oluşturucu bir istisna değildir.
Bazı nesneler, farklı alt nesneleri arasında doğrudan çapraz referanslara sahip önemsiz olmayan bir iç yapıya sahip olabilir (aslında, bu durumu tetiklemek için böyle bir dahili çapraz referans yeterlidir). Derleyici tarafından sağlanan kopya yapıcısı, dahili nesne içi ilişkileri keserek bunları nesneler arası ilişkilere dönüştürür .
Bir örnek:
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?
Nesnelerin bazı durumlarda kopyalanmasının güvenli olduğu (örneğin, varsayılan-oluşturulmuş durumda) ve başka türlü kopyalanmasının güvenli olmadığı sınıflar olabilir . Kopyalanması güvenli nesnelerin kopyalanmasına izin vermek istiyorsak, o zaman - savunmaya yönelik programlama yapılıyorsa - kullanıcı tanımlı kopya yapıcısında çalışma zamanı kontrolüne ihtiyacımız var.
Bazen kopyalanabilir olması gereken bir sınıf, kopyalanamayan alt nesneleri toplar. Genellikle bu, gözlemlenemeyen duruma sahip nesneler için olur (bu durum, aşağıdaki "Optimizasyon" bölümünde daha ayrıntılı olarak tartışılmaktadır). Derleyici yalnızca bu durumu tanımaya yardımcı olur.
Kopyalanabilir olması gereken bir sınıf, kopyalanabilir türden bir alt nesneyi toplayabilir. Yarı kopyalanabilir bir tür, tam anlamıyla bir kopya kurucu sağlamaz, ancak nesnenin kavramsal bir kopyasını oluşturmaya izin veren başka bir kurucuya sahiptir. Bir türü yarı kopyalanabilir hale getirmenin nedeni, türün kopyalama semantiği hakkında tam bir anlaşma olmamasıdır.
Örneğin, nesnenin kendi kendine kayıt durumunu tekrar gözden geçirerek, bir nesnenin yalnızca tamamen bağımsız bir nesne olması durumunda küresel nesne yöneticisine kaydedilmesi gereken durumlar olabileceğini iddia edebiliriz. Başka bir nesnenin alt nesnesiyse, onu yönetmenin sorumluluğu, kapsayıcı nesnesindedir.
Ya da hem sığ hem de derin kopyalama desteklenmelidir (hiçbiri varsayılan değildir).
Daha sonra nihai karar bu türdeki kullanıcılara bırakılır - nesneleri kopyalarken, amaçlanan kopyalama yöntemini (ek argümanlar aracılığıyla) açıkça belirtmeleri gerekir.
Programlamaya savunmasız bir yaklaşım olması durumunda, hem normal bir kopya-yapıcı hem de bir yarı-kopya-kurucunun mevcut olması da mümkündür. Nadir ancak iyi anlaşılmış durumlarda alternatif kopyalama yöntemlerinin kullanılması gerektiğinde, çoğu durumda tek bir kopyalama yönteminin uygulanması gerektiğinde bu haklı görülebilir. Bu durumda derleyici, kopya yapıcısını örtük olarak tanımlayamadığından şikayet etmez; bu türden bir alt nesnenin bir yarı-kopya-kurucu aracılığıyla kopyalanması gerekip gerekmediğini hatırlamak ve kontrol etmek kullanıcının sorumluluğunda olacaktır.
Nadir durumlarda, nesnenin gözlemlenebilir durumunun bir alt kümesi, nesnenin kimliğinin ayrılmaz bir parçasını oluşturabilir (veya kabul edilebilir) ve diğer nesnelere aktarılmamalıdır (bu biraz tartışmalı olsa da).
Örnekler:
Nesnenin UID'si (ancak bu aynı zamanda yukarıdan "kendi kendine kayıt" durumuna da aittir, çünkü kimlik bir kendi kendine kayıt işleminde elde edilmelidir).
Yeni nesnenin kaynak nesnenin geçmişini devralmaması, bunun yerine " <OTHER_OBJECT_ID> ' den <TIME> tarihinde kopyalandı " tek bir geçmiş öğesiyle başlaması gerektiğinde nesnenin geçmişi (örneğin Geri Al / Yinele yığını) .
Bu gibi durumlarda, kopya yapıcısının ilgili alt nesneleri kopyalamayı atlaması gerekir.
Derleyici tarafından sağlanan kopya oluşturucunun imzası, alt nesneler için hangi kopya yapıcılarının kullanılabilir olduğuna bağlıdır. En az bir alt nesnenin gerçek bir kopya yapıcısı yoksa (kaynak nesneyi sabit referansla alarak), bunun yerine değişen bir kopya kurucusuna sahipse (kaynak nesneyi sabit olmayan referansla alarak), derleyicinin seçeneği olmayacaktır. ancak örtük olarak bildirmek ve sonra değişen bir kopya yapıcıyı tanımlamak için.
Şimdi, ya alt nesnenin türünün "değişen" kopya yapıcısı, kaynak nesneyi gerçekten değiştirmiyorsa (ve const
anahtar kelimeyi bilmeyen bir programcı tarafından yazılmışsa )? Eksik const
olanı ekleyerek bu kodu düzeltemiyorsak, diğer seçenek, kendi kullanıcı tanımlı kopya oluşturucumuzu doğru bir imzayla bildirmek ve bir const_cast
.
Dahili verilerine doğrudan referanslar veren bir COW kabı, yapım sırasında derin kopyalanmalıdır, aksi takdirde bir referans sayma tutamacı gibi davranabilir.
COW bir optimizasyon tekniği olmasına rağmen, kopyalama yapıcısındaki bu mantık, doğru uygulaması için çok önemlidir. Bu nedenle, bu durumu, daha sonra gideceğimiz "Optimizasyon" bölümü yerine buraya yerleştirdim.
Aşağıdaki durumlarda, optimizasyon kaygıları nedeniyle kendi kopya oluşturucunuzu tanımlamak isteyebilir / buna ihtiyaç duyabilirsiniz:
Öğe kaldırma işlemlerini destekleyen bir kap düşünün, ancak bunu yalnızca kaldırılan öğeyi silinmiş olarak işaretleyerek yapabilir ve daha sonra yuvasını geri dönüştürebilir. Böyle bir kabın bir kopyası yapıldığında, "silinmiş" yuvaları olduğu gibi korumak yerine hayatta kalan verileri sıkıştırmak mantıklı olabilir.
Bir nesne, gözlemlenebilir durumunun parçası olmayan veriler içerebilir. Genellikle, bu, nesne tarafından gerçekleştirilen belirli yavaş sorgu işlemlerini hızlandırmak için nesnenin ömrü boyunca toplanan önbelleğe alınmış / belleğe alınmış verilerdir. İlgili işlemler gerçekleştirildiğinde (ve eğer!) Yeniden hesaplanacağından, bu verilerin kopyalanmasını atlamak güvenlidir. Bu verilerin kopyalanması gerekçesiz olabilir, çünkü nesnenin gözlemlenebilir durumu (önbelleğe alınan verinin türetildiği) mutasyon işlemleriyle değiştirilirse (ve nesneyi değiştirmeyeceksek, neden derin bir veri oluşturuyoruz) o zaman kopyala?)
Bu optimizasyon, yalnızca yardımcı veriler, gözlemlenebilir durumu temsil eden verilere kıyasla büyükse gerekçelendirilir.
C ++, kopya yapıcısını bildirerek örtük kopyalamayı devre dışı bırakmaya izin verir explicit
. Daha sonra bu sınıfın nesneleri işlevlere aktarılamaz ve / veya işlevlerden değere göre döndürülemez. Bu numara, hafif gibi görünen ancak kopyalanması gerçekten çok pahalı olan bir tür için kullanılabilir (yine de, kopyalanabilir hale getirmek daha iyi bir seçim olabilir).
C ++ 03'te bir kopya oluşturucunun bildirilmesi, onu da tanımlamayı gerektiriyordu (tabii ki, kullanmayı planlıyorsanız). Bu nedenle, böyle bir kopya oluşturucuya başvurmak, yalnızca tartışılan endişe dışında, derleyicinin sizin için otomatik olarak oluşturacağı aynı kodu yazmanız gerektiği anlamına gelir.
C ++ 11 ve daha yeni standartlar, özel üye işlevlerinin (varsayılan ve kopya oluşturucular, kopya atama operatörü ve yıkıcı) , varsayılan uygulamayı kullanmak için açık bir istekle (bildirimi ile sonlandırın
=default
) bildirilmesine izin verir.
Bu cevap aşağıdaki şekilde geliştirilebilir:
- Daha fazla örnek kod ekleyin
- "Dahili çapraz referanslara sahip nesneler" durumunu gösterin
- Bazı bağlantılar ekleyin
Dinamik olarak ayrılmış içeriğe sahip bir sınıfınız varsa. Örneğin, bir kitabın başlığını karakter * olarak kaydedersiniz ve başlığı yeniyle ayarlarsınız, kopya çalışmaz.
title = new char[length+1]
Ve sonra bunu yapan bir kopya oluşturucu yazmanız gerekir strcpy(title, titleIn)
. Kopya oluşturucu yalnızca "sığ" bir kopya yapacaktır.
Copy Constructor, bir nesne değere göre iletildiğinde, değere göre döndürüldüğünde veya açıkça kopyalandığında çağrılır. Kopya yapıcısı yoksa, c ++ yüzeysel bir kopya oluşturan varsayılan bir kopya yapıcısı oluşturur. Nesnenin dinamik olarak ayrılmış belleğe işaretçisi yoksa, yüzeysel kopyalama yapılacaktır.
Sınıfın özellikle ihtiyacı olmadığı sürece copy ctor ve operator = işlevini devre dışı bırakmak genellikle iyi bir fikirdir. Bu, referans amaçlandığında bir argümanı değere göre geçirmek gibi verimsizlikleri önleyebilir. Ayrıca derleyici tarafından üretilen yöntemler geçersiz olabilir.
Aşağıdaki kod parçacığını ele alalım:
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();
verileri açıkça kopyalamak için kod yazılmadan oluşturulmuş kullanıcı tanımlı bir kopya yapıcısı olduğundan gereksiz çıktı verir. Yani derleyici aynı şeyi yaratmaz.
Çoğunuz zaten biliyor olmasına rağmen, bu bilgiyi hepinizle paylaşmayı düşündüm.
Şerefe ... Mutlu kodlamalar !!!