Ktoś wspomniał o tym w IRC jako problem krojenia.
c++
inheritance
c++-faq
object-slicing
Frankomania
źródło
źródło
A a = b;
a
jest teraz obiektem typu,A
który ma kopięB::foo
. Myślę, że błędem będzie teraz go odrzucić.B b1; B b2; A& b2_ref = b2; b2 = b1
. Może uważasz, że zostały skopiowaneb1
dob2
, ale nie masz! Skopiowaniu do udziału wb1
dob2
(częścib1
, któreB
odziedziczoneA
), a lewy innych częścib2
niezmienione.b2
jest teraz stworzeniem frankensteinowskim składającym się z kilku fragmentów,b1
po których następują fragmentyb2
. Ugh! Głosowanie w dół, ponieważ myślę, że odpowiedź jest bardzo myląca.B b1; B b2; A& b2_ref = b2; b2_ref = b1
„ Prawdziwy problem występuje, jeśli ” ... wywodzisz się z klasy z nie-wirtualnym operatorem przypisania. Czy wA
ogóle jest przeznaczony do uzyskania Nie ma żadnych funkcji wirtualnych. Jeśli wywodzisz się z typu, musisz poradzić sobie z tym, że można wywoływać jego funkcje składowe!Większość odpowiedzi tutaj nie wyjaśnia, na czym polega faktyczny problem krojenia. Wyjaśniają tylko łagodne przypadki krojenia, a nie zdradzieckie. Załóżmy, podobnie jak inne odpowiedzi, że masz do czynienia z dwiema klasami
A
iB
skądB
pochodzi (publicznie)A
.W tej sytuacji, C ++ pozwala przejść instancję
B
doA
„s operatora przypisania (a także do konstruktora kopii). Działa to, ponieważ instancjęB
można przekształcić w aconst A&
, czego oczekują operatorzy przypisania i konstruktory kopiowania.Łagodna sprawa
Nie dzieje się tam nic złego - poprosiłeś o
A
egzemplarzB
, którego właśnie otrzymujesz. Jasne,a
nie będzie zawierać niektórychb
członków, ale jak powinien? WA
końcu to nie jestB
, więc nawet nie słyszało o tych członkach, nie mówiąc już o ich przechowywaniu.Zdradziecka sprawa
Możesz pomyśleć, że
b2
będzie to kopiab1
później. Ale niestety tak nie jest ! Jeśli go obejrzysz, odkryjesz, żeb2
jest stworzeniem Frankensteinowskim, zbudowanym z częścib1
(części, któreB
dziedzicząA
) i częścib2
(części, które tylkoB
zawierają). Auć!Co się stało? C ++ domyślnie nie traktuje operatorów przypisania jako
virtual
. Zatem liniaa_ref = b1
wywoła operatora przypisaniaA
, a nie operatoraB
. Wynika to z tego, że w przypadku funkcji nie wirtualnych zadeklarowany (formalnie: statyczny ) typ (który jestA&
) określa, która funkcja jest wywoływana, w przeciwieństwie do faktycznego (formalnie: dynamicznego ) typu (który byłbyB
, ponieważa_ref
odwołuje się do instancjiB
) . TerazA
operator przypisania oczywiście wie tylko o elementach zadeklarowanych wA
, więc skopiuje tylko te, pozostawiając członków dodanychB
bez zmian.Rozwiązanie
Przypisywanie tylko do części obiektu zwykle nie ma sensu, ale C ++ niestety nie zapewnia wbudowanego sposobu, aby tego zabronić. Możesz jednak rzucić własne. Pierwszym krokiem jest uczynienie operatora przypisania wirtualnym . Zagwarantuje to, że wywoływany jest zawsze operator przypisania typu rzeczywistego , a nie typu deklarowanego . Drugim krokiem jest
dynamic_cast
sprawdzenie, czy przypisany obiekt ma zgodny typ. Trzecim krokiem jest zrobić rzeczywiste zadanie w członie (chronione!)assign()
, PonieważB
„sassign()
będzie prawdopodobnie chcesz użyćA
” sassign()
skopiowaćA
„s, członkowie.Należy zauważyć, że dla czystej wygody,
B
„soperator=
covariantly nadpisuje typ zwracany, ponieważ wie, że to powrót instancjęB
.źródło
derived
wartość może być podana kodowi, że oczekujebase
wartości, albo dowolne pochodne odniesienie może być użyte jako odniesienie podstawowe. Chciałbym zobaczyć język z systemem czcionek, który osobno odnosi się do obu pojęć. Istnieje wiele przypadków, w których referencyjne pochodne powinny być podstawialne dla referencyjnych baz, ale instancje pochodne nie powinny zastępować bazowych; istnieje również wiele przypadków, w których instancje powinny być konwertowalne, ale odwołania nie powinny zastępować.Jeśli masz klasę podstawową
A
i pochodnąB
, możesz wykonać następujące czynności.Teraz metoda
wantAnA
wymaga kopiiderived
. Jednak obiektuderived
nie można skopiować całkowicie, ponieważ klasaB
może wymyślić dodatkowe zmienne składowe, które nie należą do jego klasy podstawowejA
.Dlatego, aby wywołać
wantAnA
, kompilator „odcina” wszystkie dodatkowe elementy klasy pochodnej. Rezultatem może być obiekt, którego nie chcesz utworzyć, ponieważA
-object (wszystkie specjalne zachowania klasyB
zostały utracone).źródło
wantAnA
(jak sama nazwa wskazuje!) ChceA
, to właśnie to dostaje. I instancjaA
będzie zachowywać się jakA
. Jak to jest zaskakujące?derived
na typA
. Niejawne rzutowanie jest zawsze źródłem nieoczekiwanego zachowania w C ++, ponieważ często trudno jest zrozumieć na podstawie lokalnego kodu, że miało miejsce rzutowanie.To są wszystkie dobre odpowiedzi. Chciałbym tylko dodać przykład wykonania przy przekazywaniu obiektów według wartości vs przez referencję:
Dane wyjściowe to:
źródło
Trzecie dopasowanie w Google dla „C ++ slicing” daje mi ten artykuł w Wikipedii http://en.wikipedia.org/wiki/Object_slicing i ten (nagrzany, ale kilka pierwszych postów określa problem): http://bytes.com/ forum / thread163565.html
Tak więc, kiedy przypisujesz obiekt podklasy do superklasy. Nadklasa nic nie wie o dodatkowych informacjach w podklasie i nie ma miejsca na ich przechowywanie, więc dodatkowe informacje zostają „odcięte”.
Jeśli te linki nie dostarczają wystarczających informacji do „dobrej odpowiedzi”, edytuj swoje pytanie, aby dać nam znać, czego więcej szukasz.
źródło
Problem krojenia jest poważny, ponieważ może powodować uszkodzenie pamięci i bardzo trudno jest zagwarantować, że program go nie dotknie. Aby zaprojektować go z języka, klasy obsługujące dziedziczenie powinny być dostępne tylko przez odniesienie (a nie przez wartość). Język programowania D ma tę właściwość.
Rozważ klasę A i klasę B wywodzącą się z A. Zepsucie pamięci może się zdarzyć, jeśli część A ma wskaźnik p i instancję B wskazującą p na dodatkowe dane B. Następnie, gdy dodatkowe dane zostają odcięte, p wskazuje na śmieci.
źródło
Derived
można je domyślnie przekonwertować naBase
.) Jest to oczywiście sprzeczne z zasadą otwartego zamknięcia i dużym obciążeniem konserwacyjnym.W C ++ obiekt klasy pochodnej można przypisać do obiektu klasy bazowej, ale inny sposób nie jest możliwy.
Wycinanie obiektów ma miejsce, gdy obiekt klasy pochodnej jest przypisany do obiektu klasy bazowej, dodatkowe atrybuty obiektu klasy pochodnej są odcinane w celu utworzenia obiektu klasy bazowej.
źródło
Problem krojenia w C ++ wynika z semantyki wartości jego obiektów, która pozostała głównie ze względu na kompatybilność ze strukturami C. Musisz użyć jawnej odwołania lub składni wskaźnika, aby osiągnąć „normalne” zachowanie obiektu występujące w większości innych języków, które wykonują obiekty, tj. Obiekty są zawsze przekazywane przez odniesienie.
Krótkie odpowiedzi są takie, że pocinasz obiekt przez przypisanie obiektu pochodnego do obiektu bazowego według wartości , tzn. Pozostały obiekt jest tylko częścią obiektu pochodnego. Aby zachować semantykę wartości, krojenie jest rozsądnym zachowaniem i ma swoje stosunkowo rzadkie zastosowania, które nie istnieją w większości innych języków. Niektórzy uważają, że jest to cecha C ++, podczas gdy wielu uważało ją za jedną z dziwactw / błędów w C ++.
źródło
struct
, kompatybilnością lub innym brakiem wyczucia, jak powiedział ci dowolny przypadkowy ksiądz OOP.Base
musi zająć dokładniesizeof(Base)
bajty w pamięci, z możliwym wyrównaniem, być może dlatego „przypisanie” (na stosie ) nie kopiuje pochodnych członków klasy, ich przesunięcia są poza rozmiarem. Aby uniknąć „utraty danych”, po prostu używaj wskaźnika, jak każdy inny, ponieważ pamięć wskaźnika jest ustalona na miejscu i ma rozmiar, a stos jest bardzo zmiennyWięc ... Dlaczego utrata uzyskanych informacji jest zła? ... ponieważ autor klasy pochodnej mógł zmienić reprezentację tak, że odcięcie dodatkowych informacji zmienia wartość reprezentowaną przez obiekt. Może się to zdarzyć, jeśli klasa pochodna zostanie użyta do buforowania reprezentacji, która jest bardziej wydajna dla niektórych operacji, ale kosztowna jest jej powrót do reprezentacji podstawowej.
Pomyślałem również, że ktoś powinien również wspomnieć o tym, co należy zrobić, aby uniknąć krojenia ... Uzyskaj kopię Standardów kodowania C ++, wytycznych 101 zasad i najlepszych praktyk. Radzenie sobie z krojeniem to # 54.
Sugeruje to nieco wyrafinowany wzorzec, aby w pełni poradzić sobie z tym problemem: mieć chroniony konstruktor kopii, chroniony czysty wirtualny DoClone i publiczny klon z aser, który powie ci, czy (dodatkowa) klasa pochodna nie zaimplementowała poprawnie DoClone. (Metoda klonowania tworzy odpowiednią głęboką kopię obiektu polimorficznego.)
Możesz także zaznaczyć konstruktora kopiowania w bazie jako jawny, co pozwala na wyraźne krojenie, jeśli jest to pożądane.
źródło
1. DEFINICJA PROBLEMU KROJENIA
Jeśli D jest klasą pochodną klasy bazowej B, wówczas można przypisać obiekt typu Derived do zmiennej (lub parametru) typu Base.
PRZYKŁAD
Chociaż powyższe przypisanie jest dozwolone, wartość przypisana zmiennemu zwierzakowi traci pole rasy. Nazywa się to problemem krojenia .
2. JAK NAPRAWIĆ PROBLEM Z KROJENIEM
Aby pokonać problem, używamy wskaźników do zmiennych dynamicznych.
PRZYKŁAD
W takim przypadku żaden element danych lub funkcja elementu zmiennej dynamicznej wskazywanej przez ptrD (obiekt klasy potomnej) nie zostanie utracony. Ponadto, jeśli chcesz użyć funkcji, funkcja musi być funkcją wirtualną.
źródło
dog
nie należą do klasyPet
(elementbreed
danych) nie są kopiowane do zmiennejpet
?Pet
Najwyraźniej kod jest zainteresowany tylko elementami danych. Krojenie jest zdecydowanie „problemem”, jeśli jest niepożądane, ale nie widzę tego tutaj.((Dog *)ptrP)
” Sugeruję użyciestatic_cast<Dog*>(ptrP)
Dog::breed
), nie jest w żaden sposób BŁĄD związany z SLICINGEM?Wydaje mi się, że krojenie nie jest tak dużym problemem, jak wtedy, gdy twoje własne klasy i program są źle zaprojektowane / zaprojektowane.
Jeśli przekażę obiekt podklasy jako parametr metodzie, która przyjmuje parametr typu superklasa, z pewnością powinienem o tym wiedzieć i wiedzieć wewnętrznie, wywoływana metoda będzie działać tylko z obiektem nadklasy (aka klasa podstawowa).
Wydaje mi się, że jedynie nierozsądne oczekiwanie, że zapewnienie podklasy, w której żądana jest klasa podstawowa, w jakiś sposób doprowadziłoby do określonych wyników podklasy, spowodowałoby problem z krojeniem. Jest to albo zły projekt w użyciu metody, albo słaba implementacja podklasy. Domyślam się, że zwykle jest to wynikiem poświęcenia dobrego projektu OOP na rzecz korzyści lub wzrostu wydajności.
źródło
OK, spróbuję po przeczytaniu wielu postów wyjaśniających krojenie obiektów, ale nie w jaki sposób staje się to problematyczne.
Zły scenariusz, który może spowodować uszkodzenie pamięci, jest następujący:
źródło
Wycinanie oznacza, że dane dodane przez podklasę są odrzucane, gdy obiekt tej podklasy jest przekazywany lub zwracany przez wartość lub z funkcji oczekującej obiektu klasy podstawowej.
Objaśnienie: Rozważ następującą deklarację klasy:
Ponieważ funkcje kopiowania klasy podstawowej nie wiedzą nic o pochodnej, kopiowana jest tylko podstawowa część pochodnej. Jest to powszechnie określane jako krojenie.
źródło
źródło
gdy obiekt klasy pochodnej jest przypisany do obiektu klasy bazowej, dodatkowe atrybuty obiektu klasy pochodnej są odcinane (odrzucane) z obiektu klasy bazowej.
źródło
Gdy obiekt klasy pochodnej jest przypisany do obiektu klasy bazowej, wszystkie elementy obiektu klasy pochodnej są kopiowane do obiektu klasy bazowej, z wyjątkiem elementów, które nie są obecne w klasie bazowej. Te elementy są usuwane przez kompilator. Nazywa się to Wycinaniem obiektów.
Oto przykład:
Wygeneruje:
źródło
Właśnie natknąłem się na problem krojenia i natychmiast wylądowałem tutaj. Pozwólcie, że dodam do tego moje dwa centy.
Weźmy przykład z „kodu produkcyjnego” (lub czegoś, co zbliża się trochę):
Powiedzmy, że mamy coś, co wywołuje działania. Na przykład interfejs centrum sterowania.
Ten interfejs użytkownika musi uzyskać listę rzeczy, które można obecnie wysłać. Definiujemy więc klasę, która zawiera informacje o wysyłce. Nazwijmy to
Action
. A więcAction
ma pewne zmienne składowe. Dla uproszczenia mamy po prostu 2, czyli astd::string name
i astd::function<void()> f
. Następnie ma element,void activate()
który właśnie wykonujef
członka.Tak więc interfejs użytkownika jest
std::vector<Action>
dostarczany. Wyobraź sobie niektóre funkcje, takie jak:Teraz ustaliliśmy, jak to wygląda z punktu widzenia interfejsu użytkownika. Jak dotąd żaden problem. Ale jakiś inny facet, który pracuje nad tym projektem, nagle decyduje, że istnieją specjalne działania, które wymagają więcej informacji w
Action
obiekcie. Z jakiego powodu kiedykolwiek. Można to również rozwiązać za pomocą ujęć lambda. Ten przykład nie jest wzięty z kodu 1-1.Więc facet wywodzi się z
Action
dodawania własnego smaku.Podaje przykład swojej warzonej w domu klasy,
push_back
ale potem program oszalał.Więc co się stało?
Jak można się domyślić: obiekt został plasterkach.
Dodatkowe informacje z instancji zostały utracone i
f
są teraz podatne na niezdefiniowane zachowanie.Mam nadzieję, że ten przykład daje światło o dla tych ludzi, którzy nie mogą sobie wyobrazić rzeczy, gdy mówimy o
A
s iB
s są uzyskane w jakiś sposób.źródło