c++ - O que é objeto fatiar?




inheritance c++-faq (12)

"Fatiar" é onde você atribui um objeto de uma classe derivada a uma instância de uma classe base, perdendo assim parte da informação - parte dela é "cortada".

Por exemplo,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Portanto, um objeto do tipo B possui dois membros de dados, foo e bar .

Então, se você fosse escrever isso:

B b;

A a = b;

Então a informação em b sobre bar membro é perdida em a .

Alguém mencionou isso no IRC como o problema do corte.


A maioria das respostas aqui não explica o problema real do fatiamento. Eles só explicam os casos benignos de cortar, não os traiçoeiros. Suponha, como as outras respostas, que você está lidando com duas classes A e B , onde B deriva (publicamente) de A

Nessa situação, o C ++ permite que você passe uma instância de B para o operador de atribuição de A (e também para o construtor de cópia). Isso funciona porque uma instância de B pode ser convertida em uma const A& , que é o que operadores de atribuição e construtores de cópia esperam que seus argumentos sejam.

O caso benigno

B b;
A a = b;

Nada de ruim acontece lá - você pediu uma instância de A que é uma cópia de B , e é exatamente isso que você recebe. Claro, não vai conter alguns membros de b , mas como deve? É um A , afinal, não um B , então nem sequer ouviu falar sobre esses membros, muito menos seria capaz de armazená-los.

O caso traiçoeiro

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

Você pode pensar que b2 será uma cópia de b1 depois. Mas, infelizmente, não é! Se você inspecionar, você descobrirá que b2 é uma criatura Frankensteiniana, feita de alguns pedaços de b1 (os pedaços que B herda de A ), e alguns pedaços de b2 (os pedaços que só B contém). Ai!

O que aconteceu? Bem, C ++ por padrão não trata os operadores de atribuição como virtual . Assim, a linha a_ref = b1 chamará o operador de atribuição de A , não o de B Isso ocorre porque para funções não virtuais, o tipo declarado (que é A& ) determina qual função é chamada, em oposição ao tipo real (que seria B , já que a_ref referência a uma instância de B ). Agora, o operador de atribuição de A, obviamente, sabe apenas sobre os membros declarados em A , então ele copiará apenas aqueles, deixando os membros adicionados em B inalterados.

Uma solução

Atribuir apenas a partes de um objeto geralmente faz pouco sentido, mas o C ++ infelizmente não oferece nenhuma maneira interna de proibir isso. Você pode, no entanto, rolar o seu próprio. O primeiro passo é tornar o operador de atribuição virtual . Isso garantirá que seja sempre o operador de atribuição do tipo real que é chamado, e não o tipo declarado . A segunda etapa é usar dynamic_cast para verificar se o objeto atribuído tem um tipo compatível. O terceiro passo é fazer a atribuição real em um membro (protegido!) assign() , uma vez que o assign() B provavelmente usará o assign() para copiar os membros de A

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

Note que, por pura conveniência, o operator= B operator= covariantly sobrescreve o tipo de retorno, uma vez que ele sabe que está retornando uma instância de B


Em C ++, um objeto de classe derivada pode ser atribuído a um objeto de classe base, mas o outro caminho não é possível.

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
}

O fatiamento de objetos acontece quando um objeto de classe derivada é atribuído a um objeto de classe base, os atributos adicionais de um objeto de classe derivado são cortados para formar o objeto de classe base.


Encontre respostas semelhantes aqui: http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html

Slicing significa que os dados adicionados por uma subclasse são descartados quando um objeto da subclasse é passado ou retornado por valor ou de uma função que espera um objeto de classe base.

Explicação: Considere a seguinte declaração de classe:

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

Como as funções de cópia baseclass não sabem nada sobre o derivado, apenas a parte base do derivado é copiada. Isso é comumente chamado de fatiamento.


Essas são todas boas respostas. Gostaria apenas de adicionar um exemplo de execução ao passar objetos por valor vs por referência:

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

A saída é:

Call by reference
I am a 'B'

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

O problema de fatiamento é sério porque pode resultar em corrupção de memória, e é muito difícil garantir que um programa não sofra com isso. Para projetá-lo fora da linguagem, as classes que suportam herança devem ser acessíveis apenas por referência (não por valor). A linguagem de programação D possui essa propriedade.

Considere classe A e classe B derivada de A. A corrupção de memória pode ocorrer se a parte A tiver um ponteiro p e uma instância B que aponta p para os dados adicionais de B. Então, quando os dados adicionais são cortados, p está apontando para lixo.


OK, vou tentar depois de ler muitos posts explicando o fatiamento de objetos, mas não como isso se torna problemático.

O cenário vicioso que pode resultar em corrupção de memória é o seguinte:

  • Classe fornece (acidentalmente, possivelmente gerado pelo compilador) atribuição em uma classe base polimórfica.
  • O cliente copia e divide uma instância de uma classe derivada.
  • Cliente chama uma função de membro virtual que acessa o estado fatiado.

Parece-me que cortar não é um problema a não ser quando suas próprias aulas e programas são mal arquitetados / projetados.

Se eu passar um objeto de subclasse como um parâmetro para um método, que usa um parâmetro do tipo superclasse, eu certamente deveria estar ciente disso e saber internamente, o método chamado estará trabalhando apenas com o objeto superclasse (aka baseclass).

Parece-me apenas a expectativa irrazoável de que fornecer uma subclasse onde uma baseclass é solicitada, de alguma forma resultaria em resultados específicos de subclasses, faria com que o slicing fosse um problema. Seu design é fraco no uso do método ou em uma implementação de subclasse deficiente. Eu estou supondo que é geralmente o resultado de sacrificar um bom design OOP em favor de ganhos de conveniência ou desempenho.


Quando um objeto de classe derivada é atribuído a um objeto de classe base, os atributos adicionais de um objeto de classe derivada são cortados (descartados) do objeto de 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
}

Se você tem uma classe base A e uma classe derivada B , então você pode fazer o seguinte.

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

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

Agora o método wantAnA precisa de uma cópia do derived . No entanto, o objeto derived não pode ser copiado completamente, já que a classe B poderia inventar variáveis ​​de membro adicionais que não estão em sua classe base A

Portanto, para chamar wantAnA , o compilador " wantAnA " todos os membros adicionais da classe derivada. O resultado pode ser um objeto que você não deseja criar, porque

  • pode estar incompleto
  • ele se comporta como um objeto A (todo o comportamento especial da classe B é perdido).

1. A DEFINIÇÃO DO PROBLEMA DE CORDA

Se D é uma classe derivada da classe base B, então você pode atribuir um objeto do tipo Derivado a uma variável (ou parâmetro) do tipo Base.

EXEMPLO

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

Embora a atribuição acima seja permitida, o valor atribuído à variável pet perde seu campo de raça. Isso é chamado de problema de fatiamento .

2. COMO FIXAR O PROBLEMA DE CORDA

Para derrotar o problema, usamos ponteiros para variáveis ​​dinâmicas.

EXEMPLO

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

Neste caso, nenhum dos membros de dados ou funções de membro da variável dinâmica sendo apontada por ptrD (objeto de classe descendente) será perdido. Além disso, se você precisar usar funções, a função deve ser uma função virtual.


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




object-slicing