Che cos'è lo slicing degli oggetti?


Risposte:


609

"Slicing" è il punto in cui si assegna un oggetto di una classe derivata a un'istanza di una classe base, perdendo così parte delle informazioni, alcune delle quali vengono "tagliate" via.

Per esempio,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Quindi un oggetto di tipo Bha due membri di dati fooe bar.

Quindi se dovessi scrivere questo:

B b;

A a = b;

Quindi le informazioni in bsu membro barsono perse a.


66
Molto informativo, ma vedi stackoverflow.com/questions/274626#274636 per un esempio di come si verifica lo slicing durante le chiamate al metodo (il che sottolinea il pericolo un po 'meglio dell'esempio di assegnazione semplice).
Blair Conrad,

55
Interessante. Ho programmato in C ++ per 15 anni e questo problema non mi è mai venuto in mente, poiché ho sempre passato oggetti per riferimento in termini di efficienza e stile personale. Mostra come le buone abitudini possono aiutarti.
Karl Bielefeldt,

10
@Felix Grazie ma non penso che il cast back (dato che non è un puntatore aritmetico) funzionerà, A a = b; aora è un oggetto di tipo Ache ne ha una copia B::foo. Sarà un errore riaverlo indietro ora penso.

37
Questo non è "affettare", o almeno una variante benigna di esso. Il vero problema si verifica se lo fai B b1; B b2; A& b2_ref = b2; b2 = b1. Si potrebbe pensare che hai copiato b1a b2, ma non avere! È stato copiato un parte di b1al b2(la parte di b1quello Bereditato da A), e ha lasciato le altre parti del b2invariate. b2è ora una creatura frankensteiniana composta da alcuni frammenti di b1seguiti da alcuni pezzi di b2. Ugh! Downvoting perché penso che la risposta sia molto fuorviante.
fgp,

24
@fgp Il tuo commento dovrebbe contenere B b1; B b2; A& b2_ref = b2; b2_ref = b1" Il vero problema si verifica se tu " ... deriva da una classe con un operatore di assegnazione non virtuale. È Aanche destinato alla derivazione? Non ha funzioni virtuali. Se derivate da un tipo, dovete affrontare il fatto che le sue funzioni membro possono essere chiamate!
curiousguy,

510

La maggior parte delle risposte non riesce a spiegare quale sia il vero problema con lo slicing. Spiegano solo i casi benigni di affettare, non quelli insidiosi. Supponi, come le altre risposte, che hai a che fare con due classi Ae B, da dove Bderiva (pubblicamente) A.

In questa situazione, C ++ permette di passare un'istanza di Ba A's operatore di assegnazione (e anche per il costruttore di copia). Questo funziona perché un'istanza di Bpuò essere convertita in a const A&, che è ciò che gli operatori di assegnazione e i costruttori di copie si aspettano che siano i loro argomenti.

Il caso benigno

B b;
A a = b;

Non succede nulla di brutto lì: hai chiesto un'istanza di Acui è una copia B, ed è esattamente quello che ottieni. Certo, anon conterrà alcuni bmembri, ma come dovrebbe? Dopotutto è un Anon B, quindi non ha nemmeno sentito parlare di questi membri, figuriamoci sarebbe in grado di memorizzarli.

Il caso infido

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

Potresti pensare che b2sarà una copia di b1dopo. Ma, ahimè, non lo è ! Se lo ispezionerai, scoprirai che b2è una creatura frankensteiniana, composta da alcuni pezzi di b1(i pezzi da cui Beredita A) e alcuni pezzi di b2(i pezzi che Bcontengono solo ). Ahia!

Quello che è successo? Bene, C ++ di default non tratta gli operatori di assegnazione come virtual. Pertanto, la linea a_ref = b1chiamerà l'operatore di assegnazione di A, non quello di B. Questo perché, per le funzioni non virtuali, il tipo dichiarato (formalmente: statico ) (che è A&) determina quale funzione viene chiamata, al contrario del tipo effettivo (formalmente: dinamico ) (che sarebbe B, dal momento che fa a_refriferimento a un'istanza di B) . Ora, Al'operatore di assegnazione ovviamente conosce solo i membri dichiarati in A, quindi copia solo quelli, lasciando i membri aggiunti Binvariati.

Una soluzione

L'assegnazione solo a parti di un oggetto di solito ha poco senso, ma C ++, sfortunatamente, non fornisce alcun modo incorporato per vietarlo. Tuttavia, puoi farlo tu stesso. Il primo passo è rendere virtuale l' operatore di assegnazione . Ciò garantirà che sia sempre chiamato l'operatore di assegnazione del tipo effettivo , non il tipo dichiarato . Il secondo passaggio consiste nell'utilizzare dynamic_castper verificare che l'oggetto assegnato abbia un tipo compatibile. Il terzo passo è quello di fare l'assegnazione effettiva a un membro (protetto!) assign(), Dal momento che B's assign()probabilmente vorranno utilizzare A' il assign()copiare A's, i membri.

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

Si noti che, per pura comodità, Bil operator=comando covariant ha la precedenza sul tipo restituito, poiché sa che sta restituendo un'istanza di B.


12
IMHO, il problema è che ci sono due diversi tipi di sostituibilità che possono essere impliciti dall'ereditarietà: o si derivedpuò dare qualsiasi valore al codice in attesa di un basevalore, o qualsiasi riferimento derivato può essere usato come riferimento di base. Vorrei vedere una lingua con un sistema di tipi che affronta entrambi i concetti separatamente. Ci sono molti casi in cui un riferimento derivato dovrebbe essere sostituibile con un riferimento di base, ma le istanze derivate non dovrebbero essere sostituibili con un riferimento di base; ci sono anche molti casi in cui le istanze dovrebbero essere convertibili ma i riferimenti non dovrebbero sostituirsi.
supercat

16
Non capisco cosa c'è di così brutto nel tuo caso "infido". Hai dichiarato di voler: 1) ottenere un riferimento a un oggetto di classe A e 2) lanciare l'oggetto b1 in classe A e copiarne i contenuti in un riferimento di classe A. Ciò che è effettivamente sbagliato qui è la logica corretta dietro il codice dato. In altre parole, hai scattato una piccola cornice di immagine (A), l'hai posizionata su un'immagine più grande (B) e hai dipinto attraverso quella cornice, lamentandoti in seguito che la tua immagine più grande ora sembra brutta :) Ma se consideriamo solo quell'area inquadrata, sembra abbastanza buono, proprio come voleva il pittore, giusto? :)
Mladen B.

12
Il problema è, in altre parole, che C ++ per impostazione predefinita presuppone un tipo molto forte di sostituibilità - richiede che le operazioni della classe base funzionino correttamente su istanze di sottoclassi. E questo anche per le operazioni che il compilatore ha generato automaticamente come compito. Quindi non è sufficiente non rovinare le proprie operazioni in questo senso, è necessario disabilitare esplicitamente anche quelle sbagliate generate dal compilatore. O, naturalmente, stai lontano dall'eredità pubblica, che di solito è sempre un buon suggerimento ;-)
fgp

14
Un altro approccio comune è semplicemente disabilitare l'operatore di copia e assegnazione. Per le classi all'interno della gerarchia ereditaria, in genere non vi è motivo di utilizzare il valore anziché il riferimento o il puntatore.
Siyuan Ren,

13
Cosa? Non avevo idea che gli operatori potessero essere contrassegnati come virtuali
paulm

154

Se hai una classe base Ae una classe derivata B, puoi fare quanto segue.

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

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

Ora il metodo ha wantAnAbisogno di una copia di derived. Tuttavia, l'oggetto derivednon può essere copiato completamente, poiché la classe Bpotrebbe inventare ulteriori variabili membro che non si trovano nella sua classe base A.

Pertanto, per chiamare wantAnA, il compilatore "troncerà" tutti i membri aggiuntivi della classe derivata. Il risultato potrebbe essere un oggetto che non volevi creare, perché

  • potrebbe essere incompleto,
  • si comporta come un oggetto A(tutto il comportamento speciale della classe Bviene perso).

41
C ++ non è Java! Se wantAnA(come suggerisce il nome!) Vuole un A, allora è quello che ottiene. E un'istanza di A, si comporterà come una A. Com'è sorprendente?
fgp,

83
@fgp: è sorprendente, perché non passi una A alla funzione.
Black

10
@fgp: il comportamento è simile. Tuttavia, per il programmatore C ++ medio potrebbe essere meno ovvio. Per quanto ho capito la domanda, nessuno si "lamenta". Si tratta solo di come il compilatore gestisce la situazione. Imho, è meglio evitare il taglio passando passaggi (const).
Nero

9
@ThomasW No, non butterei via l'eredità, ma uso i riferimenti. Se la firma di wantAnA fosse nulla wantAnA (const A & myA) , allora non ci sarebbe stato il taglio. Al contrario, viene passato un riferimento di sola lettura all'oggetto del chiamante.
Nero,

14
il problema riguarda principalmente il cast automatico che il compilatore esegue dal derivedtipo A. Il cast implicito è sempre una fonte di comportamenti inaspettati in C ++, perché spesso è difficile capire guardando il codice localmente che ha avuto luogo un cast.
pqnet,

41

Queste sono tutte buone risposte. Vorrei solo aggiungere un esempio di esecuzione quando si passano oggetti per valore vs per riferimento:

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

L'output è:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'

Ciao. Ottima risposta ma ho una domanda. Se faccio qualcosa del genere ** dev d; base * b = & d; ** Viene eseguita anche l'affettatura?
Adrian,

@Adrian Se si introducono alcune nuove funzioni membro o variabili membro nella classe derivata, queste non sono accessibili direttamente dal puntatore della classe base. Tuttavia è ancora possibile accedervi dall'interno delle funzioni virtuali della classe base sovraccaricata. Vedi questo: godbolt.org/z/LABx33
Vishal Sharma il

30

La terza corrispondenza su Google per "C ++ slicing" mi dà questo articolo di Wikipedia http://en.wikipedia.org/wiki/Object_slicing e questo (accesi, ma i primi post definiscono il problema): http://bytes.com/ forum / thread163565.html

Quindi è quando assegni un oggetto di una sottoclasse alla superclasse. La superclasse non è a conoscenza delle informazioni aggiuntive nella sottoclasse e non ha spazio per memorizzarle, quindi le informazioni aggiuntive vengono "troncate".

Se quei link non forniscono abbastanza informazioni per una "buona risposta", modifica la tua domanda per farci sapere che cosa stai cercando.


29

Il problema di slicing è grave perché può causare il danneggiamento della memoria ed è molto difficile garantire che un programma non ne risenta. Per progettarlo fuori dalla lingua, le classi che supportano l'ereditarietà dovrebbero essere accessibili solo come riferimento (non per valore). Il linguaggio di programmazione D ha questa proprietà.

Considerare la classe A e la classe B derivata da A. La corruzione della memoria può verificarsi se la parte A ha un puntatore p e un'istanza B che punta p ai dati aggiuntivi di B. Quindi, quando i dati aggiuntivi vengono troncati, p indica immondizia.


3
Spiegare come può verificarsi il danneggiamento della memoria.
foraidt

4
Ho dimenticato che il copiatrice ripristinerà il vptr, errore mio. Ma puoi comunque ottenere corruzione se A ha un puntatore e B lo imposta in modo che punti nella sezione B che viene tagliata.
Walter Bright,

18
Questo problema non si limita solo all'affettatura. Tutte le classi che contengono puntatori avranno comportamenti dubbi con un operatore di assegnazione predefinito e un costruttore di copie.
Weeble,

2
@Weeble - Ecco perché in questi casi si sostituisce il distruttore predefinito, l'operatore di assegnazione e il costruttore di copie.
Bjarke Freund-Hansen,

7
@Weeble: Ciò che rende peggio degli oggetti peggio delle correzioni generali del puntatore è che per essere certi di aver impedito che si verifichi lo slicing, una classe base deve fornire costruttori di conversione per ogni classe derivata . (Perché? Tutte le classi derivate che sono mancate sono suscettibili di essere raccolte dal copiatore della classe base, dal momento che Derivedè implicitamente convertibile in Base.) Ciò è ovviamente contrario al principio aperto-chiuso e un grande onere di manutenzione.
j_random_hacker,

11

In C ++, un oggetto classe derivato può essere assegnato a un oggetto classe base, ma non è possibile viceversa.

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
}

La suddivisione in oggetti si verifica quando un oggetto di classe derivata viene assegnato a un oggetto di classe base, gli attributi aggiuntivi di un oggetto di classe derivata vengono troncati per formare l'oggetto di classe base.


8

Il problema di slicing in C ++ deriva dalla semantica di valore dei suoi oggetti, che è rimasta principalmente a causa della compatibilità con le strutture C. È necessario utilizzare il riferimento esplicito o la sintassi del puntatore per ottenere un comportamento "normale" degli oggetti che si trova nella maggior parte degli altri linguaggi che fanno oggetti, ovvero gli oggetti vengono sempre passati per riferimento.

La risposta breve è che si suddivide l'oggetto assegnando un oggetto derivato a un oggetto base per valore , ovvero l'oggetto rimanente è solo una parte dell'oggetto derivato. Al fine di preservare la semantica del valore, il slicing è un comportamento ragionevole e ha i suoi usi relativamente rari, che non esiste nella maggior parte delle altre lingue. Alcune persone lo considerano una caratteristica del C ++, mentre molti lo consideravano una delle stranezze / malfunzionamenti del C ++.


5
" comportamento normale" dell'oggetto "non è" comportamento normale dell'oggetto ", è riferimento semantico . E non si riferisce in alcun modo a C struct, compatibilità o altri non-senso che qualsiasi sacerdote OOP casuale ti ha detto.
curioso

4
@curiousguy Amen, fratello. È triste vedere quanto spesso il C ++ viene cestinato dal non essere Java, quando la semantica del valore è una delle cose che rende il C ++ così follemente potente.
fgp,

Questa non è una caratteristica, non una stranezza / malfunzionamento. Si tratta di un normale comportamento di copia in pila, poiché la chiamata di una funzione con arg o (stessa) allocazione di una variabile di tipo stack Basedeve richiedere esattamente sizeof(Base)byte in memoria, con possibile allineamento, forse, ecco perché "assegnazione" (copia in pila ) non copierà i membri della classe derivati, i loro offset sono fuori dimensione. Per evitare "la perdita di dati", basta usare il puntatore, come chiunque altro, poiché la memoria del puntatore è fissata in posizione e dimensioni, mentre lo stack è molto volatile
Croll

Sicuramente un malfunzionamento del C ++. L'assegnazione di un oggetto derivato a un oggetto base dovrebbe essere vietata, mentre l'associazione di un oggetto derivato a un riferimento o un puntatore della classe base dovrebbe essere OK.
John Z. Li,

7

Quindi ... Perché perdere le informazioni derivate è male? ... perché l'autore della classe derivata potrebbe aver modificato la rappresentazione in modo tale che tagliando le informazioni extra si modifica il valore rappresentato dall'oggetto. Ciò può accadere se la classe derivata viene utilizzata per memorizzare nella cache una rappresentazione più efficiente per determinate operazioni, ma costosa per tornare alla rappresentazione di base.

Ho anche pensato che qualcuno dovrebbe anche menzionare cosa dovresti fare per evitare lo slicing ... Ottieni una copia degli standard di codifica C ++, 101 linee guida delle regole e migliori pratiche. Trattare con affettare è # 54.

Suggerisce un modello un po 'sofisticato per affrontare completamente il problema: avere un costruttore di copie protetto, un DoClone virtuale puro protetto e un clone pubblico con un'asserzione che ti dirà se una (ulteriore) classe derivata non è riuscita a implementare correttamente DoClone. (Il metodo Clone crea una copia profonda corretta dell'oggetto polimorfico.)

È anche possibile contrassegnare esplicitamente il costruttore di copie sulla base, che consente lo slicing esplicito, se desiderato.


3
" Puoi anche contrassegnare il costruttore di copie sulla base esplicita " che non aiuta affatto.
curioso

6

1. LA DEFINIZIONE DI PROBLEMA DI AFFETTAMENTO

Se D è una classe derivata della classe base B, è possibile assegnare un oggetto di tipo Derivato a una variabile (o parametro) di tipo Base.

ESEMPIO

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

Sebbene l'assegnazione di cui sopra sia consentita, il valore assegnato all'animale variabile perde il suo campo di razza. Questo si chiama problema di slicing .

2. COME FISSARE IL PROBLEMA DI AFFETTAMENTO

Per risolvere il problema, utilizziamo i puntatori alle variabili dinamiche.

ESEMPIO

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

In questo caso, nessuno dei membri dei dati o delle funzioni membro della variabile dinamica a cui punta ptrD (oggetto di classe discendente) andrà perso. Inoltre, se è necessario utilizzare le funzioni, la funzione deve essere una funzione virtuale.


7
Capisco la parte "slicing", ma non capisco il "problema". Come è un problema che alcuni stati dogche non fanno parte della classe Pet(il breedmembro di dati) non vengano copiati nella variabile pet? Il codice è interessato solo ai Petmembri dei dati - apparentemente. Affettare è sicuramente un "problema" se è indesiderato, ma non lo vedo qui.
curioso

4
" ((Dog *)ptrP)" Suggerisco di usarestatic_cast<Dog*>(ptrP)
curiousguy il

Suggerisco di far notare che alla fine la stringa "razza" perderà la memoria senza un distruttore virtuale (il distruttore di "stringa" non verrà chiamato) quando si elimina tramite "ptrP" ... Perché ciò che si presenta problematico? La correzione è per lo più un corretto design di classe. Il problema in questo caso è che scrivere i costruttori per controllare la visibilità quando si eredita è noioso e facilmente dimenticabile. Non arriverai da nessuna parte vicino alla zona di pericolo con il tuo codice in quanto non vi è alcun polimorfismo coinvolto o addirittura menzionato (il taglio troncerà il tuo oggetto ma non farà andare in crash il tuo programma, qui).
Amico,

24
-1 Questo non spiega completamente il problema reale. Il C ++ ha una semantica di valore, non una semantica di riferimento come Java, quindi tutto ciò è del tutto prevedibile. E la "correzione" è davvero un esempio di codice C ++ veramente orribile . "Risolvere" problemi inesistenti come questo tipo di suddivisione ricorrendo all'allocazione dinamica è una ricetta per codice errato, memoria trapelata e prestazioni orribili. Si noti che ci sono casi in cui lo slicing è errato, ma questa risposta non riesce a indicarli. Suggerimento: il problema inizia se si assegna tramite riferimenti .
fgp,

Capisci persino che tentare di accedere a un membro di tipo non definito ( Dog::breed) non è in alcun modo un ERRORE correlato allo SLICING?
Croll

4

Mi sembra che il taglio non sia tanto un problema se non quando le tue classi e il tuo programma sono scarsamente progettati / progettati.

Se passo un oggetto sottoclasse come parametro a un metodo, che accetta un parametro di tipo superclasse, dovrei certamente esserne consapevole e conoscere internamente, il metodo chiamato funzionerà solo con l'oggetto superclasse (aka baseclass).

Mi sembra solo l'irragionevole aspettativa che fornire una sottoclasse in cui è richiesta una baseclass, provocherebbe in qualche modo risultati specifici per la sottoclasse, causerebbe un problema al taglio. È una progettazione scadente nell'uso del metodo o un'implementazione di una sottoclasse scadente. Immagino che di solito sia il risultato del sacrificio di un buon design OOP a favore della convenienza o delle prestazioni.


3
Ma ricorda, Minok, che NON stai passando un riferimento a quell'oggetto. Stai passando una NUOVA copia di quell'oggetto, ma stai usando la classe base per copiarla nel processo.
Arafangion,

copia / assegnazione protette sulla classe base e questo problema è risolto.
Amico,

1
Hai ragione. È buona norma utilizzare classi di base astratte o limitare l'accesso alla copia / assegnazione. Tuttavia, non è così facile individuarlo una volta che è lì e facile dimenticarsene. Chiamare metodi virtuali con sliced ​​* questo può far accadere cose misteriose se ti allontani senza una violazione di accesso.
Amico,

1
Ricordo dai miei corsi di programmazione C ++ all'università che c'erano buone pratiche permanenti che per ogni classe che abbiamo creato, ci veniva richiesto di scrivere costruttori predefiniti, costruttori di copie e operatori di assegnazione, nonché un distruttore. In questo modo ti sei assicurato che la costruzione della copia e simili avvenisse nel modo in cui ne avevi bisogno, mentre scrivevi la classe ... piuttosto che in seguito si presentarono alcuni comportamenti strani.
Minok,

3

OK, ci proverò dopo aver letto molti post che spiegano lo slicing degli oggetti, ma non come diventa problematico.

Lo scenario vizioso che può provocare il danneggiamento della memoria è il seguente:

  • La classe fornisce l'assegnazione (accidentalmente, possibilmente generata dal compilatore) su una classe di base polimorfica.
  • Il client copia e suddivide un'istanza di una classe derivata.
  • Il client chiama una funzione membro virtuale che accede allo stato separato.

3

L'affettatura significa che i dati aggiunti da una sottoclasse vengono eliminati quando un oggetto della sottoclasse viene passato o restituito in base al valore o da una funzione in attesa di un oggetto della classe base.

Spiegazione: considerare la seguente dichiarazione di classe:

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

Poiché le funzioni di copia della baseclass non sanno nulla del derivato, viene copiata solo la parte di base del derivato. Questo è comunemente indicato come affettare.


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

4
Ti dispiacerebbe dare qualche dettaglio in più? In che modo la tua risposta differisce da quelle già pubblicate?
Alexis Pigeon,

2
Immagino che ulteriori spiegazioni non sarebbero male.
crochet

-1

quando un oggetto della classe derivata viene assegnato a un oggetto della classe base, gli attributi aggiuntivi di un oggetto della classe derivata vengono separati (scartati) dall'oggetto della classe base.

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
}

-1

Quando un oggetto classe derivata viene assegnato all'oggetto classe base, tutti i membri dell'oggetto classe derivata vengono copiati nell'oggetto classe base tranne i membri che non sono presenti nella classe base. Questi membri vengono eliminati dal compilatore. Questo si chiama Object Slicing.

Ecco un esempio:

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

Genererà:

[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'

Sottovalutato perché non è un buon esempio. Non funzionerebbe neanche se invece di copiare d in b, si usasse un puntatore nel qual caso esisterebbero d ed e ma Base non ha quei membri. Il tuo esempio mostra solo che non puoi accedere ai membri che la classe non ha.
Stefan Fabian,

-2

Ho appena incontrato il problema del taglio e sono subito arrivato qui. Vorrei quindi aggiungere i miei due centesimi a questo.

Facciamo un esempio da "codice di produzione" (o qualcosa che si avvicina):


Diciamo che abbiamo qualcosa che invia azioni. Un'interfaccia utente del centro di controllo, ad esempio.
Questa interfaccia utente deve ottenere un elenco di elementi che possono essere attualmente spediti. Quindi definiamo una classe che contiene le informazioni di spedizione. Chiamiamolo Action. Quindi Actionha alcune variabili membro. Per semplicità ne abbiamo solo 2, essendo a std::string namee a std::function<void()> f. Quindi ha un oggetto void activate()che esegue il fmembro.

Quindi l'interfaccia utente viene std::vector<Action>fornita. Immagina alcune funzioni come:

void push_back(Action toAdd);

Ora abbiamo stabilito come appare dal punto di vista dell'interfaccia utente. Nessun problema finora. Ma un altro ragazzo che lavora a questo progetto decide improvvisamente che ci sono azioni specializzate che richiedono maggiori informazioni Actionnell'oggetto. Per quale motivo mai. Ciò potrebbe essere risolto anche con acquisizioni lambda. Questo esempio non è preso 1-1 dal codice.

Quindi il ragazzo deriva da Actionaggiungere il suo sapore.
Passa un'istanza della sua classe prodotta in casa al push_backma poi il programma va in tilt.

Allora, cos'è successo?
Come si potrebbe avere indovinato: l'oggetto è stato tranciato.

Le informazioni aggiuntive dall'istanza sono state perse e fsono ora soggette a comportamenti indefiniti.


Spero che questo esempio porti luce per quelle persone che non riescono davvero a immaginare le cose quando si parla di As e Bs derivate in qualche modo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.