Birisi IRC'de dilimleme sorunu olarak bahsetti.
Birisi IRC'de dilimleme sorunu olarak bahsetti.
Yanıtlar:
"Dilimleme", türetilmiş bir sınıfın bir nesnesini bir temel sınıf örneğine atadığınız ve böylece bilginin bir kısmını kaybettiğiniz yerdir - bazıları "dilimlenir".
Örneğin,
class A {
int foo;
};
class B : public A {
int bar;
};
Yani bir nesnenin B
iki veri üyesi vardır foo
ve bar
.
Eğer bunu yazacak olsaydınız:
B b;
A a = b;
Sonra b
üye hakkında bilgi bar
kaybolur a
.
A a = b;
a
, şimdi A
kopya olan tür nesnedir B::foo
. Sanırım şimdi geri dökmek yanlış olur.
B b1; B b2; A& b2_ref = b2; b2 = b1
. Sen Kopyaladığınız düşünebilir b1
için b2
ama sen var! Bir kopyaladığınız kısmı arasında b1
hiç b2
(bir parçası b1
olduğu B
miras A
) ve diğer bölümlerini sol b2
değişmeden. b2
Şimdi bir kaç bitten oluşan frankeştayn yaratıktır b1
bazı parçaları izledi b2
. Ihh! Aşağı oylama, çünkü cevabın çok yanıltıcı olduğunu düşünüyorum.
B b1; B b2; A& b2_ref = b2; b2_ref = b1
" Gerçek sorun, siz " sanal olmayan atama işlecine sahip bir sınıftan türetmeniz durumunda ortaya çıkacaktır . A
Hatta türetme amaçlı mıdır ? Sanal işlevi yoktur. Bir türden türetiyorsanız, üye işlevlerinin çağrılabileceği gerçeğiyle uğraşmak zorundasınız!
Buradaki cevapların çoğu dilimleme ile ilgili asıl sorunun ne olduğunu açıklayamıyor. Hain olanları değil, sadece iyi huylu dilimleme vakalarını açıklarlar. Diğer cevaplar gibi, iki sınıfla uğraştığınızı A
ve (herkese açık) B
nereden B
kaynaklandığını varsayın A
.
Bu durumda, C ++ size bir örneğini geçmesine izin verir B
etmek A
'ın atama operatörü (ve ayrıca kopya yapıcısına). Bunun nedeni , atama işleçlerinin ve kopya oluşturucularının argümanlarının olmasını beklediği şey olan bir örneği a'ya B
dönüştürülebilir const A&
.
B b;
A a = b;
Orada kötü bir şey olmaz - bunun bir örneği A
olan bir kopyasını istediniz B
ve tam olarak bunu elde edersiniz. Tabii, a
bazı b
üyelerini içermeyecek , ama nasıl olmalı? Her A
şeyden önce, bir değil B
, bu yüzden onları saklayabilse bile , bu üyeleri bile duymamıştı .
B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!
Bunun daha sonra b2
bir kopyası olacağını düşünebilirsiniz b1
. Ama ne yazık ki öyle değil ! Eğer incelerseniz, bunun b2
bazı parçalarından b1
( B
miras alınan parçalar A
) ve bazı parçalarından b2
(sadece B
içeren parçalar ) yapılmış bir Frankensteinian yaratık olduğunu keşfedeceksiniz . Ah!
Ne oldu? Aslında, C ++ varsayılan olarak atama işleçlerini işlemez virtual
. Böylece, hat a_ref = b1
, atama operatörünü A
değil , atama operatörünü çağırır B
. Bu sanal olmayan fonksiyonlar için, çünkü ilan : (resmen statik tipi (olan) A&
) karşıt olarak, denir fonksiyonu belirleyen gerçek (resmi: Dinamik ) türü (olurdu B
, çünkü a_ref
referansları bir örneği B
) . Şimdi, A
atama operatörü açıkça beyan edilen üyeler hakkında bilgi A
sahibi olduğundan, üyeleri B
değiştirmeden ekleyerek yalnızca üyeler kopyalanacak .
Bir nesnenin yalnızca parçalarına atamak genellikle çok mantıklı değildir, ancak C ++ maalesef bunu yasaklamak için yerleşik bir yol sağlamaz. Ancak kendiniz de yapabilirsiniz. İlk adım atama işlecini sanal hale getirmektir . Bu hep olduğunu garanti edecek fiili denir türünün atama operatörü değil beyan gibiler. İkinci adım, dynamic_cast
atanan nesnenin uyumlu bir türe sahip olduğunu doğrulamaktır. Üçüncü adım, bir (korumalı!) Üyesi gerçek atama yapmaktır assign()
, çünkü B
'ler assign()
muhtemelen kullanmak isteyeceğiniz A
' s assign()
kopyalamak için A
, 'in üyeleri.
class A {
public:
virtual A& operator= (const A& a) {
assign(a);
return *this;
}
protected:
void assign(const A& a) {
// copy members of A from a to this
}
};
class B : public A {
public:
virtual B& operator= (const A& a) {
if (const B* b = dynamic_cast<const B*>(&a))
assign(*b);
else
throw bad_assignment();
return *this;
}
protected:
void assign(const B& b) {
A::assign(b); // Let A's assign() copy members of A from b to this
// copy members of B from b to this
}
};
Saf kolaylık, Not B
's operator=
o zamandan beri covariantly, dönüş türü geçersiz kılar bilir o bir örneğini döndürüyor olduğunu B
.
derived
değer bekleyen koda herhangi bir değer verilebilir base
ya da herhangi bir türetilmiş referans temel referans olarak kullanılabilir. Her iki kavramı ayrı ayrı ele alan bir yazı sistemi olan bir dil görmek istiyorum. Türetilmiş bir referansın bir baz referansının yerine geçmesi gereken birçok durum vardır, ancak türetilmiş örneklerin taban referanslarının yerine geçmemesi gerekir; örneklerin dönüştürülebilir olması ancak referansların yerine geçmemesi gereken birçok durum da vardır.
Bir temel sınıfınız A
ve türetilmiş bir sınıfınız varsa B
, aşağıdakileri yapabilirsiniz.
void wantAnA(A myA)
{
// work with myA
}
B derived;
// work with the object "derived"
wantAnA(derived);
Şimdi yöntemin wantAnA
bir kopyası gerekiyor derived
. Ancak, derived
sınıf B
temel sınıfında olmayan ek üye değişkenleri icat edebileceğinden nesne tamamen kopyalanamaz A
.
Bu nedenle, çağırmak wantAnA
için derleyici türetilmiş sınıfın tüm ek üyelerini "keser". Sonuç, oluşturmak istemediğiniz bir nesne olabilir, çünkü
A
-nesne gibi davranır (sınıfın tüm özel davranışları B
kaybolur).wantAnA
(adından da anlaşılacağı gibi!) Bir istiyorsa A
, o zaman bu olur. Ve bir örneği A
gibi davranacak A
. Bu nasıl şaşırtıcı?
derived
türden gerçekleştirdiği otomatik dökümde A
. Örtülü döküm her zaman C ++ 'da beklenmeyen bir davranış kaynağıdır, çünkü koda yerel olarak bir dökümün gerçekleştiğine bakmaktan anlamak zordur.
Bunların hepsi iyi cevaplar. Ben sadece referans ile değer vs nesneleri geçerken bir yürütme örneği eklemek istiyorum:
#include <iostream>
using namespace std;
// Base class
class A {
public:
A() {}
A(const A& a) {
cout << "'A' copy constructor" << endl;
}
virtual void run() const { cout << "I am an 'A'" << endl; }
};
// Derived class
class B: public A {
public:
B():A() {}
B(const B& a):A(a) {
cout << "'B' copy constructor" << endl;
}
virtual void run() const { cout << "I am a 'B'" << endl; }
};
void g(const A & a) {
a.run();
}
void h(const A a) {
a.run();
}
int main() {
cout << "Call by reference" << endl;
g(B());
cout << endl << "Call by copy" << endl;
h(B());
}
Çıktı:
Call by reference
I am a 'B'
Call by copy
'A' copy constructor
I am an 'A'
Google'da "C ++ dilimleme" için üçüncü maç bana bu Wikipedia makalesini http://en.wikipedia.org/wiki/Object_slicing ve bu (ısıtmalı, ancak ilk birkaç mesaj sorunu tanımlar) verir: http://bytes.com/ forum / thread163565.html
Süper sınıfa bir alt sınıf nesnesi atadığınızda. Üst sınıf, alt sınıftaki ek bilgilerin hiçbirini bilmez ve saklamak için yer yoktur, bu nedenle ek bilgiler "dilimlenir".
Bu bağlantılar "iyi yanıt" için yeterli bilgi vermezse, daha fazla aradığınızı bize bildirmek için lütfen sorunuzu düzenleyin.
Dilimleme sorunu ciddidir çünkü bellek bozulmasına neden olabilir ve bir programın bundan muzdarip olmadığını garanti etmek çok zordur. Dili dilden çıkarmak için, kalıtımı destekleyen sınıflara yalnızca referans olarak erişilebilir (değere göre değil). D programlama dili bu özelliğe sahiptir.
A sınıfını ve A sınıfından türetilmiş B sınıfını düşünün. A parçasında p işaretçisi ve p'nin B ek verilerine işaret eden bir B örneği varsa bellek bozulması oluşabilir. Daha sonra, ek veriler dilimlendiğinde, p çöplere işaret eder.
Derived
, dolaylı olarak dönüştürülebilir olduğundan , temel sınıfın kopya ctoru tarafından alınmaya Base
açıktır.) Bu, Açık-Kapalı Prensip'e ve büyük bir bakım yüküne açıkça karşıdır.
C ++ 'da, türetilmiş bir sınıf nesnesi temel sınıf nesnesine atanabilir, ancak diğer yol mümkün değildir.
class Base { int x, y; };
class Derived : public Base { int z, w; };
int main()
{
Derived d;
Base b = d; // Object Slicing, z and w of d are sliced off
}
Nesne dilimleme, türetilmiş bir sınıf nesnesi bir temel sınıf nesnesine atandığında, türetilmiş bir sınıf nesnesinin ek nitelikleri, temel sınıf nesnesini oluşturmak için dilimlenir.
C ++ 'da dilimleme sorunu, çoğunlukla C yapılarıyla uyumluluk nedeniyle kalan nesnelerinin değer semantiğinden kaynaklanmaktadır. Nesneleri yapan diğer birçok dilde bulunan "normal" nesne davranışını elde etmek için açık referans veya işaretçi sözdizimi kullanmanız gerekir; örneğin, nesneler her zaman başvuru ile iletilir.
Kısa cevaplar, bir temel nesneye değere göre türetilmiş bir nesne atayarak nesneyi dilimlemenizdir , yani kalan nesne türetilmiş nesnenin yalnızca bir parçasıdır. Değer semantiğini korumak için dilimleme makul bir davranıştır ve diğer birçok dilde mevcut olmayan nispeten nadir kullanımları vardır. Bazıları bunu C ++ 'ın bir özelliği olarak görürken, birçoğu onu C ++' ın tuhaflıklarından / yanlışlıklarından biri olarak görüyor.
struct
.
Base
tam olarak sizeof(Base)
bayt alması gerektiğinden , yığın üzerinde kopyalama davranışı normaldir , belki de bu nedenle "atama" (yığın üzerinde kopyalama) ) türetilmiş sınıf üyelerini kopyalamaz, ofsetleri sizeof dışındadır. İşaretçi bellek yeri ve büyüklüğü sabit olduğundan yığın çok Uçucu oysa, herkes gibi, sadece kullanım işaretçisi, "Veri kaybını" önlemek için
Peki ... Elde edilen bilgileri kaybetmek neden kötü? ... çünkü türetilmiş sınıfın yazarı temsili değiştirmiş olabilir, böylece fazladan bilginin kesilmesi nesne tarafından temsil edilen değeri değiştirir. Bu, türetilmiş sınıfın, belirli işlemler için daha verimli, ancak temel gösterime geri dönüşü pahalı olan bir gösterimi önbelleğe almak için kullanılırsa oluşabilir.
Ayrıca birinin dilimlemekten kaçınmak için ne yapmanız gerektiğini de belirtmesi gerektiğini düşündüm ... C ++ Kodlama Standartları, 101 kural kılavuzları ve en iyi uygulamaların bir kopyasını alın. Dilimleme ile uğraşmak # 54.
Sorunu tamamen ele almak için biraz karmaşık bir model önermektedir: korumalı bir kopya oluşturucu, korumalı saf sanal DoClone ve (daha fazla) türetilmiş bir sınıfın DoClone'u doğru bir şekilde uygulayamadığını söyleyecek bir onaylayıcıya sahip bir genel Klon bulundurun. (Klonlama yöntemi, polimorfik nesnenin derinlemesine bir kopyasını oluşturur.)
İsterseniz, açık dilimlemeye izin veren temel oluşturucuda kopya kurucusunu da işaretleyebilirsiniz.
1. DİLİMLEME SORUNUNUN TANIMI
D temel sınıf B'nin türetilmiş bir sınıfıysa, Tür türündeki bir değişkene (veya parametreye) Türetilmiş türünde bir nesne atayabilirsiniz.
MİSAL
class Pet
{
public:
string name;
};
class Dog : public Pet
{
public:
string breed;
};
int main()
{
Dog dog;
Pet pet;
dog.name = "Tommy";
dog.breed = "Kangal Dog";
pet = dog;
cout << pet.breed; //ERROR
Yukarıdaki atamaya izin verilse de, değişken evcil hayvana atanan değer cins alanını kaybeder. Buna dilimleme problemi denir .
2. DİLİMLEME SORUNUNUN DÜZELTİLMESİ
Sorunu yenmek için dinamik değişkenlere işaretçiler kullanıyoruz.
MİSAL
Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed;
Bu durumda, ptrD (alt sınıf nesnesi) tarafından gösterilen dinamik değişkenin veri üyeleri veya üye işlevleri kaybolmaz. Ayrıca, işlevleri kullanmanız gerekirse, işlevin sanal bir işlev olması gerekir.
dog
Sınıfın bir parçası olmayan bir durumun Pet
( breed
veri üyesi) değişkene kopyalanmaması nasıl bir problemdir pet
? Kod sadece Pet
veri üyeleriyle ilgileniyor - görünüşe göre. İstenmeyen dilimleme kesinlikle bir "problem" dir, ancak burada göremiyorum.
((Dog *)ptrP)
" Kullanmanızı öneririmstatic_cast<Dog*>(ptrP)
Dog::breed
) türünde bir üyeye erişmeye çalışmanın DİLİMLEME ile ilgili bir HATA'nın yolu olmadığını anlıyor musunuz ?
Bana öyle geliyor ki, dilimleme, kendi derslerinizin ve programınızın zayıf bir şekilde tasarlandığı / tasarlandığı durumdan başka bir sorun değil.
Bir alt sınıf nesnesini parametre olarak superclass türünde bir parametre alan bir yönteme geçirirsem, bunun farkında olmalı ve içsel olarak bilmeliyim, çağrılan yöntem yalnızca üst sınıf (aka baseclass) nesnesiyle çalışacaktır.
Bana öyle geliyor ki, sadece bir temel sınıfın talep edildiği bir alt sınıfın sunulmasının, bir şekilde alt sınıfa özgü sonuçlara yol açması, dilimlemenin bir sorun olmasına neden olacağı mantıksız beklentisi. Ya yöntem kullanımında zayıf tasarımı ya da kötü bir alt sınıf uygulaması. Genellikle iyi OOP tasarımından yararlanma veya performans kazanımları lehine feda etmenin sonucudur.
Tamam, nesne dilimlemeyi açıklayan birçok gönderiyi okuduktan sonra nasıl deneyeceğim ama nasıl sorunlu hale geldiğini değil.
Bellek bozulmasına neden olabilecek kısır senaryo şudur:
Dilimleme, bir alt sınıf tarafından eklenen verilerin, alt sınıfın bir nesnesi değere göre aktarıldığında veya döndürüldüğünde veya bir temel sınıf nesnesini bekleyen bir işlevden atıldığı anlamına gelir.
Açıklama: Aşağıdaki sınıf bildirimini göz önünde bulundurun:
class baseclass
{
...
baseclass & operator =(const baseclass&);
baseclass(const baseclass&);
}
void function( )
{
baseclass obj1=m;
obj1=m;
}
Temel sınıf kopyalama işlevleri türetilmiş hakkında hiçbir şey bilmediğinden, türetilenin yalnızca temel kısmı kopyalanır. Bu genellikle dilimleme olarak adlandırılır.
class A
{
int x;
};
class B
{
B( ) : x(1), c('a') { }
int x;
char c;
};
int main( )
{
A a;
B b;
a = b; // b.c == 'a' is "sliced" off
return 0;
}
türetilmiş bir sınıf nesnesi bir temel sınıf nesnesine atandığında, türetilmiş bir sınıf nesnesinin ek nitelikleri temel sınıf nesnesinden dilimlenir (atılır).
class Base {
int x;
};
class Derived : public Base {
int z;
};
int main()
{
Derived d;
Base b = d; // Object Slicing, z of d is sliced off
}
Temel sınıf Nesnesine Türetilmiş bir sınıf Nesnesi atandığında, türetilmiş sınıf nesnesinin tüm üyeleri, temel sınıfta bulunmayan üyeler dışında temel sınıf nesnesine kopyalanır. Bu üyeler derleyici tarafından dilimlenir. Buna Nesne Dilimleme denir.
İşte bir örnek:
#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
int a;
int b;
int c;
Base()
{
a=10;
b=20;
c=30;
}
};
class Derived : public Base
{
public:
int d;
int e;
Derived()
{
d=40;
e=50;
}
};
int main()
{
Derived d;
cout<<d.a<<"\n";
cout<<d.b<<"\n";
cout<<d.c<<"\n";
cout<<d.d<<"\n";
cout<<d.e<<"\n";
Base b = d;
cout<<b.a<<"\n";
cout<<b.b<<"\n";
cout<<b.c<<"\n";
cout<<b.d<<"\n";
cout<<b.e<<"\n";
return 0;
}
Üretecek:
[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'
Ben sadece dilimleme sorunu koştu ve hemen buraya indi. İki sentimi buna ekleyeyim.
"Üretim kodundan" bir örnek alalım (ya da yakın gelen bir şey):
Diyelim ki eylemler gönderen bir şeyimiz var. Örneğin bir kontrol merkezi kullanıcı arayüzü.
Bu kullanıcı arayüzünün şu anda gönderilebilen şeylerin bir listesini alması gerekiyor. Bu nedenle, gönderim bilgilerini içeren bir sınıf tanımlarız. Haydi diyelim Action
. Yani bir Action
üye değişkenleri vardır. Sadelik için a std::string name
ve a olmak üzere sadece 2 tane var std::function<void()> f
. Sonra void activate()
sadece birf
üyeyi .
Böylece UI std::vector<Action>
tedarik edilir. Şöyle bazı işlevleri düşünün:
void push_back(Action toAdd);
Şimdi arayüzün nasıl göründüğünü belirledik. Şimdiye kadar sorun yok. Ancak bu proje üzerinde çalışan başka bir adam aniden Action
nesnede daha fazla bilgiye ihtiyaç duyan özel eylemler olduğuna karar veriyor . Hangi sebepten ötürü. Bu da lambda yakalamaları ile çözülebilir. Bu örnek koddan 1-1 alınmaz.
Böylece adam Action
kendi lezzetini eklemekten geliyor.
Evde demlenmiş sınıfının bir örneğini geçirir, push_back
ancak program haywire'a gider.
Peki ne oldu?
Tahmin edebileceğiniz gibi: nesne dilimlenmiştir.
Örneğe ait fazladan bilgiler kaybolmuştur ve f
şimdi tanımlanmamış davranışa eğilimlidir.
Ben bu örnek bahsederken gerçekten şeyleri hayal bile edemeyen insanlar için yaklaşık ışık getiriyor umut A
s ve B
bazı biçimde kullanılmasıyla elde edilen s.