Co to jest krojenie obiektów?

Odpowiedzi:

608

„Krojenie” polega na przypisywaniu obiektu klasy pochodnej do instancji klasy bazowej, co powoduje utratę części informacji - część z nich jest „krojona”.

Na przykład,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Zatem obiekt typu Bma dwóch członków danych fooi bar.

Jeśli miałbyś to napisać:

B b;

A a = b;

Następnie informacje w belemencie członkowskim barzostaną utracone a.

David Dibben
źródło
66
Bardzo pouczające, ale zobacz stackoverflow.com/questions/274626#274636, aby zobaczyć przykład podziału na plastry podczas wywołań metod (który podkreśla niebezpieczeństwo nieco lepiej niż zwykły przykład przypisania).
Blair Conrad,
55
Ciekawy. Programuję w C ++ od 15 lat i ten problem nigdy nie przyszedł mi do głowy, ponieważ zawsze przekazywałem obiekty przez odniesienie ze względu na wydajność i osobisty styl. Pokazuje, jak dobre nawyki mogą ci pomóc.
Karl Bielefeldt
10
@ Felix Dzięki, ale nie sądzę, że rzutowanie z powrotem (ponieważ nie jest to arytmetyka wskaźnika) zadziała, A a = b; ajest teraz obiektem typu, Aktóry ma kopię B::foo. Myślę, że błędem będzie teraz go odrzucić.
37
To nie jest „krojenie”, a przynajmniej łagodny wariant. Prawdziwy problem występuje, jeśli tak zrobisz B b1; B b2; A& b2_ref = b2; b2 = b1. Może uważasz, że zostały skopiowane b1do b2, ale nie masz! Skopiowaniu do udziału w b1do b2(części b1, które Bodziedziczone A), a lewy innych części b2niezmienione. b2jest teraz stworzeniem frankensteinowskim składającym się z kilku fragmentów, b1po których następują fragmenty b2. Ugh! Głosowanie w dół, ponieważ myślę, że odpowiedź jest bardzo myląca.
fgp
24
@fgp Twój komentarz powinien brzmieć B b1; B b2; A& b2_ref = b2; b2_ref = b1Prawdziwy problem występuje, jeśli ” ... wywodzisz się z klasy z nie-wirtualnym operatorem przypisania. Czy w Aogó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!
ciekawy
508

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 Ai Bskąd Bpochodzi (publicznie) A.

W tej sytuacji, C ++ pozwala przejść instancję Bdo A„s operatora przypisania (a także do konstruktora kopii). Działa to, ponieważ instancję Bmożna przekształcić w a const A&, czego oczekują operatorzy przypisania i konstruktory kopiowania.

Łagodna sprawa

B b;
A a = b;

Nie dzieje się tam nic złego - poprosiłeś o Aegzemplarz B, którego właśnie otrzymujesz. Jasne, anie będzie zawierać niektórych bczłonków, ale jak powinien? W Akońcu to nie jest B, więc nawet nie słyszało o tych członkach, nie mówiąc już o ich przechowywaniu.

Zdradziecka sprawa

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

Możesz pomyśleć, że b2będzie to kopia b1później. Ale niestety tak nie jest ! Jeśli go obejrzysz, odkryjesz, że b2jest stworzeniem Frankensteinowskim, zbudowanym z części b1(części, które Bdziedziczą A) i części b2(części, które tylko Bzawierają). Auć!

Co się stało? C ++ domyślnie nie traktuje operatorów przypisania jako virtual. Zatem linia a_ref = b1wywoła operatora przypisania A, a nie operatora B. Wynika to z tego, że w przypadku funkcji nie wirtualnych zadeklarowany (formalnie: statyczny ) typ (który jest A&) określa, która funkcja jest wywoływana, w przeciwieństwie do faktycznego (formalnie: dynamicznego ) typu (który byłby B, ponieważ a_refodwołuje się do instancji B) . Teraz Aoperator przypisania oczywiście wie tylko o elementach zadeklarowanych w A, więc skopiuje tylko te, pozostawiając członków dodanych Bbez 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_castsprawdzenie, czy przypisany obiekt ma zgodny typ. Trzecim krokiem jest zrobić rzeczywiste zadanie w członie (chronione!) assign(), Ponieważ B„s assign()będzie prawdopodobnie chcesz użyć A” s assign()skopiować A„s, członkowie.

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
  }
};

Należy zauważyć, że dla czystej wygody, B„s operator=covariantly nadpisuje typ zwracany, ponieważ wie, że to powrót instancję B.

FGP
źródło
11
IMHO, problem polega na tym, że istnieją dwa różne rodzaje podstawialności, które mogą wynikać z dziedziczenia: albo każda derivedwartość może być podana kodowi, że oczekuje basewartoś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ć.
supercat
16
Nie rozumiem, co jest tak złego w twojej „zdradzieckiej” sprawie. Stwierdziłeś, że chcesz: 1) uzyskać odwołanie do obiektu klasy A i 2) rzutować obiekt b1 do klasy A i skopiować jego zawartość do odwołania do klasy A. To, co tak naprawdę jest nie tak, to właściwa logika podany kod. Innymi słowy, wziąłeś małą ramkę obrazu (A), umieściłeś ją na większym obrazie (B) i malowałeś tę ramkę, narzekając później, że twój większy obraz wygląda teraz brzydko :) Ale jeśli weźmiemy pod uwagę ten obszar w ramce, wygląda całkiem nieźle, tak jak chciał malarz, prawda? :)
Mladen B.,
12
Problem polega, inaczej mówiąc, na tym, że C ++ domyślnie zakłada bardzo silną substytucyjność - wymaga, aby operacje klasy podstawowej działały poprawnie w instancjach podklasy. I to nawet w przypadku operacji, które kompilator wygenerował automatycznie, jak przypisanie. Więc nie wystarczy nie spieprzyć własnych operacji w tym względzie, musisz również wyraźnie wyłączyć nieprawidłowe operacje generowane przez kompilator. Lub oczywiście trzymaj się z dala od publicznego dziedzictwa, co zwykle jest dobrą sugestią ;-)
fgp
14
Innym powszechnym podejściem jest po prostu wyłączenie operatora kopiowania i przypisywania. W przypadku klas w hierarchii dziedziczenia zwykle nie ma powodu, aby używać wartości zamiast odwołania lub wskaźnika.
Siyuan Ren,
13
Co? Nie miałem pojęcia, że ​​operatorzy mogliby zostać oznaczeni wirtualnie
paulm
153

Jeśli masz klasę podstawową Ai pochodną B, możesz wykonać następujące czynności.

void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);

Teraz metoda wantAnAwymaga kopii derived. Jednak obiektu derivednie można skopiować całkowicie, ponieważ klasa Bmoże wymyślić dodatkowe zmienne składowe, które nie należą do jego klasy podstawowej A.

Dlatego, aby wywołać wantAnA, kompilator „odcina” wszystkie dodatkowe elementy klasy pochodnej. Rezultatem może być obiekt, którego nie chcesz utworzyć, ponieważ

  • może być niekompletny,
  • zachowuje się jak A-object (wszystkie specjalne zachowania klasy Bzostały utracone).
czarny
źródło
40
C ++ to nie Java! Jeśli wantAnA(jak sama nazwa wskazuje!) Chce A, to właśnie to dostaje. I instancja Abędzie zachowywać się jak A. Jak to jest zaskakujące?
fgp
82
@fgp: Zaskakujące, ponieważ nie przekazujesz litery A do funkcji.
Black
10
@fgp: Zachowanie jest podobne. Jednak dla przeciętnego programisty C ++ może to być mniej oczywiste. O ile rozumiem pytanie, nikt nie narzeka. Chodzi tylko o to, jak kompilator radzi sobie z sytuacją. Imho, lepiej w ogóle unikać krojenia, przekazując (const) referencje.
Czarny
8
@ThomasW Nie, nie wyrzucę dziedziczenia, ale użyję referencji. Jeśli podpis wantAnA byłby nieważny wantAnA (const A i myA) , to nie było cięcia . Zamiast tego przekazywane jest odwołanie tylko do odczytu do obiektu wywołującego.
Czarny
14
problem dotyczy głównie automatycznego rzutowania, z którego kompilator wykonuje dane derivedna typ A. 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.
pqnet,
41

To są wszystkie dobre odpowiedzi. Chciałbym tylko dodać przykład wykonania przy przekazywaniu obiektów według wartości vs przez referencję:

#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());
}

Dane wyjściowe to:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'
geh
źródło
Witaj. Świetna odpowiedź, ale mam jedno pytanie. Jeśli zrobię coś takiego ** dev d; baza * b = & d; ** Krojenie ma również miejsce?
Adrian
@Adrian Jeśli wprowadzisz nowe funkcje składowe lub zmienne składowe w klasie pochodnej, nie będą one dostępne bezpośrednio ze wskaźnika klasy podstawowej. Jednak nadal można uzyskać do nich dostęp z wnętrza przeciążonych funkcji wirtualnych klasy podstawowej. Zobacz: godbolt.org/z/LABx33
Vishal Sharma,
30

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.

Archetypalny Paweł
źródło
29

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.

Walter Bright
źródło
3
Proszę wyjaśnić, w jaki sposób może wystąpić uszkodzenie pamięci.
foraidt
4
Zapomniałem, że ctor kopiowania zresetuje vptr, mój błąd. Ale nadal możesz uzyskać zepsucie, jeśli A ma wskaźnik, a B ustawia go tak, aby wskazywał odcinek B, który zostaje odcięty.
Walter Bright
18
Ten problem nie ogranicza się tylko do krojenia. Wszystkie klasy zawierające wskaźniki będą miały podejrzane zachowanie z domyślnym operatorem przypisania i konstruktorem kopii.
Weeble
2
@Weeble - dlatego w tych przypadkach zastępujesz domyślny destruktor, operator przypisania i konstruktor kopii.
Bjarke Freund-Hansen
7
@Weeble: Co sprawia, że ​​krojenie obiektów jest gorsze niż ogólne poprawki wskaźnika, to aby mieć pewność, że zapobiegniesz krojeniu, klasa podstawowa musi zapewniać konstruktory konwertujące dla każdej klasy pochodnej . (Dlaczego? Wszelkie pominięte klasy pochodne są podatne na przechwycenie przez ctor klasy bazowej, ponieważ Derivedmożna je domyślnie przekonwertować na Base.) Jest to oczywiście sprzeczne z zasadą otwartego zamknięcia i dużym obciążeniem konserwacyjnym.
j_random_hacker
10

W C ++ obiekt klasy pochodnej można przypisać do obiektu klasy bazowej, ale inny sposób nie jest możliwy.

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
}

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.

Kartik Maheshwari
źródło
8

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 ++.

ididak
źródło
5
„ normalne ”zachowanie obiektu ”, które nie jest „normalnym zachowaniem obiektu”, to jest odniesienie semantyczne . I nie ma to żadnego związku z C struct, kompatybilnością lub innym brakiem wyczucia, jak powiedział ci dowolny przypadkowy ksiądz OOP.
ciekawy
4
@curiousguy Amen, bracie. Smutno jest patrzeć na to, jak często C ++ jest nieuczciwy, gdy nie jest Javą, gdy semantyka wartości jest jedną z rzeczy, które sprawiają, że C ++ jest tak niesamowicie potężny.
fgp
To nie jest cecha, nie dziwactwo / błąd. Jest to normalne zachowanie podczas kopiowania na stosie, ponieważ wywołanie funkcji z argumentem arg lub (tym samym) alokującym zmienną stosu typu Basemusi zająć dokładnie sizeof(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 zmienny
Croll
Zdecydowanie błąd C ++. Przypisywanie obiektu pochodnego do obiektu podstawowego powinno być zakazane, a wiązanie obiektu pochodnego z referencją lub wskaźnikiem klasy podstawowej powinno być OK.
John Z. Li
7

Wię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.

Steve Steiner
źródło
3
Możesz także zaznaczyć konstruktor kopiowania w bazie jako jawny ”, co wcale nie pomaga.
ciekawy
6

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

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

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

Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;         
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed; 

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ą.

haberdar
źródło
7
Rozumiem część „krojenie”, ale nie rozumiem „problemu”. W jaki sposób problem polega na tym, że niektóre z tych stanów dognie należą do klasy Pet(element breeddanych) nie są kopiowane do zmiennej pet? PetNajwyraźniej kod jest zainteresowany tylko elementami danych. Krojenie jest zdecydowanie „problemem”, jeśli jest niepożądane, ale nie widzę tego tutaj.
ciekawy,
4
((Dog *)ptrP)” Sugeruję użyciestatic_cast<Dog*>(ptrP)
ciekawy
Sugeruję wskazanie, że sprawisz, że łańcuch „hoduje” ostatecznie wyciek pamięci bez wirtualnego destruktora (destruktor „łańcucha” nie zostanie wywołany) podczas usuwania przez „ptrP” ... Dlaczego to, co pokazujesz, jest problematyczne? Ta poprawka to w większości właściwy projekt klasy. Problem w tym przypadku polega na tym, że zapisywanie konstruktorów w celu kontrolowania widoczności podczas dziedziczenia jest uciążliwe i łatwe do zapomnienia. Kod nie zbliży się do strefy niebezpiecznej, ponieważ nie występuje ani nie wspomina się o polimorfizmie (wycinanie spowoduje obcięcie obiektu, ale nie spowoduje awarii programu).
Koleś
24
-1 To całkowicie nie wyjaśnia rzeczywistego problemu. C ++ ma semantykę wartości, a nie semantykę odwołań, taką jak Java, więc należy się tego całkowicie spodziewać. A „poprawka” naprawdę jest przykładem naprawdę okropnego kodu C ++. „Naprawianie” nieistniejących problemów, takich jak tego typu krojenie poprzez zastosowanie dynamicznej alokacji, jest receptą na błędny kod, wyciek pamięci i straszną wydajność. Pamiętaj, że przypadki, w których krojenie jest złe, ale ta odpowiedź nie wskazuje ich. Wskazówka: problem zaczyna się, jeśli przypisujesz za pomocą referencji .
fgp
Czy w ogóle rozumiesz, że próba uzyskania dostępu do członka typu, który nie jest zdefiniowany ( Dog::breed), nie jest w żaden sposób BŁĄD związany z SLICINGEM?
Croll
4

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.

Minok
źródło
3
Pamiętaj jednak, Minok, że NIE podajesz referencji do tego obiektu. Przekazujesz NOWĄ kopię tego obiektu, ale używasz klasy bazowej do skopiowania jej w tym procesie.
Arafangion,
chronione kopiowanie / przypisanie do klasy podstawowej i ten problem został rozwiązany.
Koleś
1
Masz rację. Dobrą praktyką jest stosowanie abstrakcyjnych klas bazowych lub ograniczanie dostępu do kopiowania / przypisywania. Jednak nie jest tak łatwo zauważyć, kiedy tam jest i łatwo zapomnieć o opiece. Wywołanie metod wirtualnych za pomocą plastra * może sprawić, że wydarzy się coś tajemniczego, jeśli uciekniesz bez naruszenia zasad dostępu.
Koleś
1
Przypominam sobie z moich kursów programowania C ++ na uniwersytecie, że istniały najlepsze praktyki, że dla każdej klasy, którą stworzyliśmy, musieliśmy pisać domyślne konstruktory, kopiować konstruktory i operatory przypisania, a także destruktor. W ten sposób upewniłeś się, że konstrukcja kopii i tym podobne zdarzyły się tak, jak tego potrzebujesz, podczas pisania klasy ... zamiast później pojawiły się dziwne zachowania.
Minok
3

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:

  • Klasa zapewnia (przypadkowo, prawdopodobnie wygenerowane przez kompilator) przypisanie do polimorficznej klasy bazowej.
  • Klient kopiuje i wycina wystąpienie klasy pochodnej.
  • Klient wywołuje funkcję wirtualnego elementu członkowskiego, który uzyskuje dostęp do stanu odcięcia.
Koleś
źródło
3

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:

           class baseclass
          {
                 ...
                 baseclass & operator =(const baseclass&);
                 baseclass(const baseclass&);
          }
          void function( )
          {
                baseclass obj1=m;
                obj1=m;
          }

Ponieważ funkcje kopiowania klasy podstawowej nie wiedzą nic o pochodnej, kopiowana jest tylko podstawowa część pochodnej. Jest to powszechnie określane jako krojenie.

Santosh
źródło
1
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; 
}
quidkid
źródło
4
Czy mógłbyś podać dodatkowe szczegóły? Czym różni się twoja odpowiedź od już opublikowanych?
Alexis Pigeon
2
Myślę, że więcej wyjaśnień nie byłoby złe.
looper
-1

gdy obiekt klasy pochodnej jest przypisany do obiektu klasy bazowej, dodatkowe atrybuty obiektu klasy pochodnej są odcinane (odrzucane) z obiektu klasy bazowej.

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
}
Varun Kumar
źródło
-1

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:

#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;
}

Wygeneruje:

[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'
Ghulam Moinul Quadir
źródło
Przegłosowano, ponieważ to nie jest dobry przykład. Nie działałoby to również, gdyby zamiast skopiować d do b, użyłbyś wskaźnika, w którym to przypadku d i e nadal istniałyby, ale Base nie ma tych elementów. Twój przykład pokazuje tylko, że nie możesz uzyskać dostępu do członków, których klasa nie ma.
Stefan Fabian
-2

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ęc Actionma pewne zmienne składowe. Dla uproszczenia mamy po prostu 2, czyli a std::string namei a std::function<void()> f. Następnie ma element, void activate()który właśnie wykonuje fczłonka.

Tak więc interfejs użytkownika jest std::vector<Action>dostarczany. Wyobraź sobie niektóre funkcje, takie jak:

void push_back(Action toAdd);

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 Actionobiekcie. 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 Actiondodawania własnego smaku.
Podaje przykład swojej warzonej w domu klasy, push_backale 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 fsą 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 As i Bs są uzyskane w jakiś sposób.

Martin B.
źródło