Alguém o mencionou no IRC como o problema da fatia.
c++
inheritance
c++-faq
object-slicing
Frankomania
fonte
fonte
A a = b;
a
agora é objeto do tipoA
que possui cópiaB::foo
. Será um erro devolvê-lo agora, eu acho.B b1; B b2; A& b2_ref = b2; b2 = b1
. Você pode pensar que você copioub1
parab2
, mas você não tem! Você copiou uma parte dob1
queb2
(a parteb1
queB
herdou deA
), e deixou as outras partesb2
inalteradas.b2
é agora uma criatura frankensteiniana que consiste em alguns pedaçosb1
seguidos por alguns pedaços deb2
. 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
eB
, de ondeB
deriva (publicamente)A
.Nesta situação, C ++ permite que você passe uma instância
B
deA
's operador de atribuição (e também para o construtor de cópia). Isso funciona porque uma instância deB
pode ser convertida em aconst A&
, que é o que os operadores de atribuição e os construtores de cópias esperam que seus argumentos sejam.O caso benigno
Nada de ruim acontece lá - você solicitou uma instância da
A
qual é uma cópiaB
e é exatamente isso que obtém. Claro,a
não conterá algunsb
membros, mas como deveria? É umA
, afinal, não umB
, por isso nem sequer ouviu falar sobre esses membros, e muito menos seria capaz de armazená-los.O caso traiçoeiro
Você pode pensar que
b2
será uma cópiab1
posterior. Mas, infelizmente, é não ! Se você inspecionar, descobrirá queb2
é uma criatura frankensteiniana, feita de alguns pedaços deb1
(os pedaços queB
herdamA
) e alguns pedaços deb2
(pedaços queB
contêm apenas ). Ai!O que aconteceu? Bem, C ++ por padrão não trata os operadores de atribuição como
virtual
. Assim, a linhaa_ref = b1
chamará o operador de atribuição deA
, não o deB
. 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 seriaB
, poisa_ref
referencia uma instância deB
) . Agora,A
obviamente, o operador de atribuição sabe apenas sobre os membros declaradosA
, portanto ele copiará somente aqueles, deixando os membros adicionadosB
inalterados.Uma solução
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!) Membroassign()
, uma vez queB
'sassign()
provavelmente vai querer usarA
éassign()
para copiarA
's, membros.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ânciaB
.fonte
derived
valor pode ser dado ao código que espera umbase
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 derivadaB
, poderá fazer o seguinte.Agora, o método
wantAnA
precisa de uma cópia dederived
. No entanto, o objetoderived
não pode ser copiado completamente, pois a classeB
pode inventar variáveis de membro adicionais que não estão em sua classe baseA
.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, porqueA
objeto (todo comportamento especial da classeB
é perdido).fonte
wantAnA
(como o próprio nome indica!) Quer umA
, então é isso que ele recebe. E uma instância deA
, se comportará como umaA
. Como isso é surpreendente?derived
tipoA
. 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:
A saída é:
fonte
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.
fonte
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.
fonte
Derived
é implicitamente conversível emBase
.) 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.
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.
fonte
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 ++.
fonte
struct
, compatibilidade ou outro absurdo que qualquer padre aleatório de OOP lhe disse.Base
deve levar exatamentesizeof(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átilEntã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.
fonte
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
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
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.
fonte
dog
que não faz parte da classePet
(obreed
membro de dados) não seja copiado na variávelpet
? O código é apenas interessado nosPet
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.
fonte
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:
fonte
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:
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.
fonte
fonte
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.
fonte
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:
Irá gerar:
fonte
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, umAction
tem algumas variáveis de membro. Por simplicidade, temos apenas 2, sendo astd::string name
e astd::function<void()> f
. Então ele tem umvoid activate()
que apenas executa of
membro.Portanto, a interface do usuário é
std::vector<Action>
fornecida. Imagine algumas funções como: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 no
Action
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 o
push_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
asB
derivações são derivadas de alguma maneira.fonte