Alguém o mencionou no IRC como o problema da fatia.
Alguém o mencionou no IRC como o problema da fatia.
Respostas:
"Fatiar" é onde você atribui um objeto de uma classe derivada a uma instância de uma classe base, perdendo parte das informações - algumas delas são "fatiadas".
Por exemplo,
class A {
int foo;
};
class B : public A {
int bar;
};
Portanto, um objeto do tipo B
tem dois membros de dados foo
e bar
.
Então, se você escrever isso:
B b;
A a = b;
Em seguida, as informações b
sobre o membro bar
são perdidas a
.
A a = b;
a
agora é objeto do tipo A
que possui cópia B::foo
. Será um erro devolvê-lo agora, eu acho.
B b1; B b2; A& b2_ref = b2; b2 = b1
. Você pode pensar que você copiou b1
para b2
, mas você não tem! Você copiou uma parte do b1
que b2
(a parte b1
que B
herdou de A
), e deixou as outras partes b2
inalteradas. b2
é agora uma criatura frankensteiniana que consiste em alguns pedaços b1
seguidos por alguns pedaços de b2
. Ugh! Voto negativo, porque acho que a resposta é muito enganadora.
B b1; B b2; A& b2_ref = b2; b2_ref = b1
" O verdadeiro problema ocorre se você " ... deriva de uma classe com um operador de atribuição não virtual. É A
mesmo destinado a derivação? Não possui funções virtuais. Se você deriva de um tipo, precisará lidar com o fato de que suas funções-membro podem ser chamadas!
A maioria das respostas aqui não consegue explicar qual é o problema real com o fatiamento. Eles apenas explicam os casos benignos de fatiar, não os traiçoeiros. Suponha, como as outras respostas, que você está lidando com duas classes A
e B
, de onde B
deriva (publicamente) A
.
Nesta situação, C ++ permite que você passe uma instância B
de A
's operador de atribuição (e também para o construtor de cópia). Isso funciona porque uma instância de B
pode ser convertida em a const A&
, que é o que os operadores de atribuição e os construtores de cópias esperam que seus argumentos sejam.
B b;
A a = b;
Nada de ruim acontece lá - você solicitou uma instância da A
qual é uma cópia B
e é exatamente isso que obtém. Claro, a
não conterá alguns b
membros, mas como deveria? É um A
, afinal, não um B
, por isso nem sequer ouviu falar sobre esses membros, e muito menos seria capaz de armazená-los.
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 b1
posterior. Mas, infelizmente, é não ! Se você inspecionar, descobrirá que b2
é uma criatura frankensteiniana, feita de alguns pedaços de b1
(os pedaços que B
herdam A
) e alguns pedaços de b2
(pedaços que B
contêm apenas ). 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 (formalmente: estático ) (que é A&
) determina qual função é chamada, em oposição ao tipo real (formalmente: dinâmico ) (que seria B
, pois a_ref
referencia uma instância de B
) . Agora, A
obviamente, o operador de atribuição sabe apenas sobre os membros declarados A
, portanto ele copiará somente aqueles, deixando os membros adicionados B
inalterados.
Atribuir apenas partes de um objeto geralmente faz pouco sentido, mas o C ++, infelizmente, não fornece uma maneira interna de proibir isso. Você pode, no entanto, fazer o seu próprio. A primeira etapa é tornar o operador de atribuição virtual . Isso garantirá que sempre seja chamado o operador de atribuição do tipo real , 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 (protegida!) Membro assign()
, uma vez que B
's assign()
provavelmente vai querer usar A
é assign()
para copiar A
's, membros.
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-se que, por conveniência pura, B
é operator=
covariantemente substitui o tipo de retorno, uma vez que sabe que ele está retornando uma instância B
.
derived
valor pode ser dado ao código que espera um base
valor ou qualquer referência derivada pode ser usada como referência base. Eu gostaria de ver uma linguagem com um sistema de tipos que aborde os dois conceitos separadamente. Há muitos casos em que uma referência derivada deve ser substituída por uma referência base, mas as instâncias derivadas não devem ser substituídas por referências básicas; também existem muitos casos em que as instâncias devem ser conversíveis, mas as referências não devem substituir.
Se você tiver uma classe base A
e uma classe derivada B
, poderá 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 de derived
. No entanto, o objeto derived
não pode ser copiado completamente, pois a classe B
pode inventar variáveis de membro adicionais que não estão em sua classe base A
.
Portanto, para chamar wantAnA
, o compilador "cortará" todos os membros adicionais da classe derivada. O resultado pode ser um objeto que você não deseja criar, porque
A
objeto (todo comportamento especial da classe B
é perdido).wantAnA
(como o próprio nome indica!) Quer um A
, então é isso que ele recebe. E uma instância de A
, se comportará como uma A
. Como isso é surpreendente?
derived
tipo A
. A conversão implícita é sempre uma fonte de comportamento inesperado no C ++, porque geralmente é difícil entender ao examinar o código localmente que uma conversão ocorreu.
Todas essas são 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'
A terceira correspondência no google para "C ++ slicing" me fornece este artigo da Wikipedia http://en.wikipedia.org/wiki/Object_slicing e isso (aquecido, mas as primeiras postagens definem o problema): http://bytes.com/ forum / thread163565.html
Então é quando você atribui um objeto de uma subclasse à superclasse. A superclasse não sabe nada das informações adicionais na subclasse e não tem espaço para armazená-las, portanto as informações adicionais são "cortadas".
Se esses links não fornecerem informações suficientes para uma "boa resposta", edite sua pergunta para nos informar o que mais você está procurando.
O problema de fatiar é 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 do idioma, 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 a classe A e a classe B derivada de A. A corrupção da memória pode ocorrer se a parte A tiver um ponteiro p e uma instância B que aponte p para os dados adicionais de B. Então, quando os dados adicionais são cortados, p está apontando para o lixo.
Derived
é implicitamente conversível em Base
.) Isso obviamente é contrário ao Princípio Aberto-Fechado e a uma grande carga de manutenção.
No C ++, um objeto de classe derivado pode ser atribuído a um objeto de classe 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
}
A divisão de objetos ocorre quando um objeto de classe derivada é atribuído a um objeto de classe base, atributos adicionais de um objeto de classe derivada são cortados para formar o objeto de classe base.
O problema de fatiar em C ++ surge da semântica de valores de seus objetos, que permaneceu principalmente devido à compatibilidade com estruturas C. Você precisa usar referência explícita ou sintaxe do ponteiro para obter um comportamento "normal" do objeto encontrado na maioria das outras linguagens que fazem objetos, ou seja, os objetos sempre são passados por referência.
A resposta curta é que você divide o objeto atribuindo um objeto derivado a um objeto base por valor , ou seja, o objeto restante é apenas uma parte do objeto derivado. Para preservar a semântica do valor, o fatiamento é um comportamento razoável e tem usos relativamente raros, que não existem na maioria dos outros idiomas. Algumas pessoas o consideram um recurso do C ++, enquanto muitos o consideram uma das peculiaridades / falhas do C ++.
struct
, compatibilidade ou outro absurdo que qualquer padre aleatório de OOP lhe disse.
Base
deve levar exatamente sizeof(Base)
bytes na memória, com possível alinhamento, talvez por isso "atribuição" (cópia na pilha) ) não copiará os membros da classe derivada, suas compensações estão fora do tamanho Para evitar a "perda de dados", ponteiro uso apenas, como qualquer outra pessoa, desde que a memória ponteiro é fixado no local e tamanho, enquanto a pilha é muito volátil
Então ... Por que perder as informações derivadas é ruim? ... porque o autor da classe derivada pode ter alterado a representação de tal modo que cortar as informações extras altere o valor que está sendo representado pelo objeto. Isso pode acontecer se a classe derivada for usada para armazenar em cache uma representação que seja mais eficiente para determinadas operações, mas dispendiosa para voltar à representação base.
Também pensei que alguém deveria mencionar o que você deve fazer para evitar o fatiamento ... Obtenha uma cópia dos C ++ Coding Standards, das 101 diretrizes de regras e das melhores práticas. Lidar com fatiar é o número 54.
Ele sugere um padrão um tanto sofisticado para lidar completamente com o problema: ter um construtor de cópias protegidas, um DoClone virtual puro protegido e um Clone público com uma declaração que informará se uma classe (mais) derivada falhou em implementar o DoClone corretamente. (O método Clone faz uma cópia profunda adequada do objeto polimórfico.)
Você também pode marcar o construtor de cópia na base como explícito, o que permite fatias explícitas, se desejado.
1. A DEFINIÇÃO DE PROBLEMA DE CORTE
Se D for uma classe derivada da classe base B, você poderá 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 criação. Isso é chamado de problema de corte .
2. COMO SOLUCIONAR O PROBLEMA DE SLICING
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;
Nesse caso, nenhum membro de dados ou função de membro da variável dinâmica apontada pelo ptrD (objeto de classe descendente) será perdido. Além disso, se você precisar usar funções, a função deverá ser uma função virtual.
dog
que não faz parte da classe Pet
(o breed
membro de dados) não seja copiado na variável pet
? O código é apenas interessado nos Pet
membros dos dados - aparentemente. Cortar é definitivamente um "problema" se não for desejado, mas não vejo isso aqui.
((Dog *)ptrP)
" Sugiro usarstatic_cast<Dog*>(ptrP)
Dog::breed
) não é um erro relacionado ao SLICING?
Parece-me que fatiar não é um problema, a não ser quando suas próprias classes e programas são mal arquitetados / projetados.
Se eu passar um objeto de subclasse como parâmetro para um método, que aceita um parâmetro do tipo superclasse, certamente eu deveria estar ciente disso e conhecer internamente, o método chamado trabalhará apenas com o objeto da superclasse (também conhecida como classe básica).
Parece-me apenas a expectativa irracional de que fornecer uma subclasse em que uma classe básica é solicitada resultaria, de alguma forma, em resultados específicos da subclasse, causaria o fatiamento como um problema. Seu design é ruim no uso do método ou uma implementação de subclasse ruim. Suponho que geralmente é o resultado de sacrificar um bom design de POO em favor de conveniência ou ganhos de desempenho.
OK, vou tentar depois de ler muitas postagens 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:
Fatiar 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 de classe base não sabem nada sobre a derivada, apenas a parte base da derivada é copiada. Isso geralmente é chamado de corte.
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;
}
quando um objeto de classe derivado é atribuído a um objeto de classe base, atributos adicionais de um objeto de classe derivado são cortados (descartados) no 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
}
Quando um objeto de classe derivada é atribuído ao objeto de classe base, todos os membros do objeto de classe derivada são copiados para o objeto de classe base, exceto os membros que não estão presentes na classe base. Esses membros são cortados pelo compilador. Isso é chamado de Fatiamento de Objetos.
Aqui está um exemplo:
#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;
}
Irá gerar:
[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'
Acabei de me deparar com o problema da fatia e desembarcou imediatamente aqui. Então, deixe-me adicionar meus dois centavos a isso.
Vamos dar um exemplo de "código de produção" (ou algo que se aproxima):
Digamos que temos algo que despacha ações. Uma interface do usuário 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 classe que contém as informações de despacho. Vamos chamá-lo Action
. Portanto, um Action
tem algumas variáveis de membro. Por simplicidade, temos apenas 2, sendo a std::string name
e a std::function<void()> f
. Então ele tem um void activate()
que apenas executa of
membro.
Portanto, a interface do usuário é std::vector<Action>
fornecida. Imagine algumas funções como:
void push_back(Action toAdd);
Agora, estabelecemos a aparência da perspectiva da interface do usuário. Não há problema até agora. Mas outro cara que trabalha nesse projeto decide subitamente que existem ações especializadas que precisam de mais informações noAction
objeto. Por que razão alguma vez. Isso também pode ser resolvido com capturas lambda. Este exemplo não é obtido 1-1 do código.
Então o cara deriva Action
para adicionar seu próprio sabor.
Ele passa um exemplo de sua aula caseira para opush_back
mas depois o programa dá errado.
Então o que aconteceu?
Como você pode ter adivinhado: o objeto foi cortado.
As informações extras da instância foram perdidas e f
agora estão sujeitas a comportamentos indefinidos.
Espero que este exemplo traga luz para aquelas pessoas que realmente não conseguem imaginar coisas quando falam sobre se A
as B
derivações são derivadas de alguma maneira.