O que é object fatiar?

Alguém mencionou isso no IRC, mas o Google não tem uma boa resposta.

“Fatiar” é onde você atribui um object de uma class derivada a uma instância de uma class base, perdendo assim parte da informação – parte dela é “cortada”.

Por exemplo,

class A { int foo; }; class B : public A { int bar; }; 

Portanto, um object 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 .

A maioria das respostas aqui não consegue explicar qual é 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 classs 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 somente 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 function é 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 object geralmente faz pouco sentido, mas o C ++ infelizmente não fornece 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, não o tipo declarado . A segunda etapa é usar dynamic_cast para verificar se o object atribuído tem um tipo compatível. O terceiro passo é fazer a atribuição real em um membro (protegido!) assign() , uma vez que B ‘s assign() provavelmente desejará usar A ‘s 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(&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 conveniência pura, o operator= B operator= covariantly sobrescreve o tipo de retorno, desde que saiba que está retornando uma instância de B

Se você tem uma class base A e uma class 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 object derived não pode ser copiado completamente, já que a class B poderia inventar variables ​​de membro adicionais que não estão em sua class base A

Portanto, para chamar wantAnA , o compilador irá “cortar” todos os membros adicionais da class derivada. O resultado pode ser um object que você não deseja criar, porque

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

Terceiro jogo no google para “C ++ slicing” me dá este artigo da Wikipedia http://en.wikipedia.org/wiki/Object_slicing e isso (aquecido, mas os primeiros posts definem o problema): http://bytes.com/ forum / thread163565.html

Então é quando você atribui um object de uma subclass à superclass. A superclass não sabe nada da informação adicional na subclass, e não tem espaço para armazená-la, então a informação adicional é “cortada”.

Se esses links não fornecerem informações suficientes para uma “boa resposta”, edite sua pergunta para nos informar o que você está procurando.

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

 #include  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 do fatiamento é sério porque pode resultar em corrupção de memory, e é muito difícil garantir que um programa não sofra com isso. Para projetá-lo fora da linguagem, as classs que suportam inheritance devem ser acessíveis apenas por referência (não por valor). A linguagem de programação D possui essa propriedade.

Considere a class A e a class B derivada de A. A corrupção de memory 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.

O problema de fatiamento em C ++ surge da semântica do valor de seus objects, que permaneceu principalmente devido à compatibilidade com estruturas C. Você precisa usar uma referência explícita ou uma syntax de ponteiro para atingir o comportamento de object “normal” encontrado na maioria das outras linguagens que fazem objects, ou seja, os objects são sempre passados ​​por referência.

A resposta curta é que você divide o object atribuindo um object derivado a um object base por valor , ou seja, o object restante é apenas uma parte do object derivado. Para preservar a semântica de valor, o fatiamento é um comportamento razoável e tem seus usos relativamente raros, o que não existe na maioria das outras linguagens. Algumas pessoas consideram isso uma característica do C ++, enquanto muitos o consideraram como um dos caprichos / falhas do C ++.

1. A DEFINIÇÃO DO PROBLEMA DE CORDA

Se D é uma class derivada da class base B, então você pode atribuir um object 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 CORTE

Para derrotar o problema, usamos pointers para variables ​​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 (object de class descendente) será perdido. Além disso, se você precisar usar funções, a function deve ser uma function virtual.

Então … Por que perder as informações derivadas é ruim? … porque o autor da class derivada pode ter alterado a representação de tal forma que cortar as informações extras altera o valor que está sendo representado pelo object. Isso pode acontecer se a class derivada for usada para armazenar em cache uma representação que seja mais eficiente para determinadas operações, mas dispendiosa para se transformar novamente na representação base.

Também pensei que alguém também deve mencionar o que você deve fazer para evitar o fatiamento … Obtenha uma cópia dos padrões de codificação em C ++, 101 diretrizes de regras e práticas recomendadas. Lidar com o fatiamento é # 54.

Ele sugere um padrão um pouco sofisticado para lidar completamente com o problema: ter um construtor de cópia protegida, um DoClone virtual puro protegido e um Clone público com uma declaração que informará se uma class derivada (mais) falhou ao implementar o DoClone corretamente. (O método Clone faz uma cópia profunda apropriada do object polimórfico.)

Você também pode marcar o construtor de cópia na base explícita, o que permite o fatiamento explícito, se desejado.

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 object de subclass como um parâmetro para um método, que usa um parâmetro do tipo superclass, eu certamente deveria estar ciente disso e saber internamente, o método chamado estará trabalhando apenas com o object superclass (aka baseclass).

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

Em C ++, um object de class derivada pode ser atribuído a um object de class base, mas a outra maneira 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 objects acontece quando um object de class derivada é atribuído a um object de class base, os atributos adicionais de um object de class derivado são cortados para formar o object de class base.

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

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

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

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 subclass são descartados quando um object da subclass é passado ou retornado por valor ou de uma function que espera um object de class base.

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

  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.

 class A { int x; }; class B { B( ) : x(1), c('a') { } int x; char c; }; int main( ) { A a; B b; a = b; // bc == 'a' is "sliced" off return 0; } 

Quando um object de class Derivado é atribuído à class Base Object, todos os membros do object de class derivada são copiados para o object de class base, exceto os membros que não estão presentes na class base. Esses membros são cortados pelo compilador. Isso é chamado de fatiamento de objects.

Aqui está um exemplo:

 #include 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< 

Isso irá gerar:

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

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

Acabei de me deparar com o problema de corte e prontamente desembarquei aqui. Então deixe-me adicionar meus dois centavos para isso.

Vamos ter um exemplo de “código de produção” (ou algo que vem perto):


Vamos dizer que temos algo que despacha ações. Uma interface do centro de controle, por exemplo.
Essa interface do usuário precisa obter uma lista de itens que podem ser despachados no momento. Então, definimos uma class que contém as informações de despacho. Vamos chamá-lo de Action . Portanto, uma Action tem algumas variables ​​de membro. Para simplificar, temos apenas 2, sendo um std::string name e um std::function f . Então tem um void activate() que apenas executa o f membro.

Assim, a interface do usuário recebe um std::vector fornecido. Imagine algumas funções como:

 void push_back(Action toAdd); 

Agora, estabelecemos a aparência da perspectiva da interface do usuário. Nenhum problema até agora. Mas algum outro cara que trabalha neste projeto de repente decide que existem ações especializadas que precisam de mais informações no object Action . Por que razão nunca. Isso também poderia ser resolvido com capturas lambda. Este exemplo não é tirado 1-1 do código.

Então o cara deriva de Action para adicionar seu próprio sabor.
Ele passa um exemplo de sua aula caseira para o push_back mas depois o programa dá errado.

Então o que aconteceu?
Como você deve ter adivinhado: o object foi fatiado.

A informação extra da instância foi perdida, e agora está propensa a um comportamento indefinido.


Eu espero que este exemplo traga luz para aquelas pessoas que não podem realmente imaginar coisas quando falam sobre A e B sendo derivadas de alguma maneira.