Pergunta

Eu li sobre o problema de fatiamento em C++ e tentei alguns exemplos (tenho experiência em Java).Infelizmente, não entendo parte do comportamento.Atualmente, estou preso neste exemplo (exemplo alternativo do Efficent C++ Third Edition).Alguém pode me ajudar a entender isso?

Minha classe simples de pai:

class Parent
{
public:

    Parent(int type) { _type = type; }

    virtual std::string getName() { return "Parent"; }

    int getType() { return _type; }

private:

    int _type;
};

Minha classe simples de uma criança:

class Child : public Parent
{

public:

    Child(void) : Parent(2) {};

    virtual std::string getName() { return "Child"; }
    std::string extraString() { return "Child extra string"; }
};

O principal:

void printNames(Parent p)
{
    std::cout << "Name: " << p.getName() << std::endl;

    if (p.getType() == 2)
    {
        Child & c = static_cast<Child&>(p);
        std::cout << "Extra: " << c.extraString() << std::endl;
        std::cout << "Name after cast: " << c.getName() << std::endl;
    }
}

int main()
{
    Parent p(1);
    Child c;

    printNames(p);
    printNames(c);
}

Após a execução, recebo:

Nome:Pai

Nome:Pai

Extra:Corda extra filho

Nome após elenco:Pai

Entendo as duas primeiras linhas, porque é a causa do "fatiamento".Mas não entendo por que posso transformar o filho em pai por meio da conversão estática.No livro está escrito que após o fatiamento, todas as informações especializadas serão cortadas.Então, suponho que não posso lançar p em c, porque não tenho informações (no início da função printNames é criado um novo objeto Parent sem as informações adicionais).

Além disso, por que estou recebendo o "Nome após elenco:Pai" se a conversão foi bem-sucedida em vez de "Nome após conversão:Criança"?

Foi útil?

Solução

Seu resultado é um extraordinário golpe de azar.Este é o resultado que estou obtendo:

Name: Parent
Name: Parent
Extra: Child extra string
bash: line 8:  6391 Segmentation fault      (core dumped) ./a.out

Aqui está o que está acontecendo no seu código:

Quando você passa c para printNames, a conversão acontece em.Em particular, como a passagem é por valor, você invoca o construtor de cópia de Parent, que é declarado implicitamente e cujo código ficaria assim:

Parent(Parent const& other) : _type{other._type} {}

Em outras palavras, você copia o _type variável de c e nada mais.Você agora tem um novo objeto do tipo Parent (tanto seu tipo estático quanto dinâmico são Parent) e c não é realmente passado para printNames de forma alguma.

Dentro da função, você converte à força p para um Child&.Este elenco não pode ter sucesso porque p simplesmente não é um Child, ou conversível para um, mas C++ não fornece nenhum diagnóstico para isso (o que é realmente uma pena, já que o compilador poderia provar trivialmente que a conversão está errada).

Agora estamos na terra do comportamento indefinido e agora tudo pode acontecer.Na prática, desde Child::extraString nunca acessa this (implícita ou explicitamente), a chamada para essa função é bem-sucedida.A chamada é feita em um objeto ilegal, mas como o objeto nunca é tocado, isso funciona (mas ainda é ilegal!).

A próxima chamada, para Child::getName, é uma chamada virtual e, portanto, precisa acessar explicitamente this (na maioria das implementações, ele acessa o ponteiro da tabela de métodos virtuais).E novamente, como o código é UB de qualquer maneira, tudo pode acontecer.Você teve “sorte” e o código simplesmente pegou o ponteiro da tabela de métodos virtuais da classe pai.Com meu compilador, esse acesso obviamente falhou.

Outras dicas

Esse código é horrível.O que está acontecendo:

  • printNames(c) fatias c, construção de cópia local p de Parent objeto incorporado no chamador c objeto e, em seguida, definir pponteiro para o Parent tabela de despacho virtual.

  • porque os membros de dados de Parent foram copiados de c, o tipo de p é 2 e o if filial é inserida

  • Child & c = static_cast<Child&>(p); efetivamente diz ao compilador "confie em mim (sou um programador) e sei disso p é na verdade um Child objeto ao qual eu gostaria de fazer referência", mas isso é uma mentira descarada, pois p é na verdade um Parent objeto copiado do Child c

    • a responsabilidade recai sobre você, como programador, de não pedir ao compilador para fazer isso se não tiver certeza de que é válido
  • c.extraString() é encontrado estaticamente (em tempo de compilação) pelo compilador porque ele sabe c é um Child (ou outro tipo derivado, mas c.extraString não é virtual então pode ser resolvido estaticamente;isso é comportamento indefinido fazer isso em um Parent objeto, mas provavelmente porque extraString não tenta acessar nenhum dado que apenas um Child objeto teria, ele aparentemente funcionou "ok" para você

  • c.getName() é virtual, então o compilador usa a tabela de despacho virtual do objeto - porque o objeto é realmente um Parent ele resolve dinamicamente (em tempo de execução) para o Parent::getName função e produz a saída associada

    • a implementação do despacho virtual é definida pela implementação, e seu comportamento indefinido pode não funcionar dessa maneira em todas as implementações de C++, ou mesmo em todos os níveis de otimização, com todas as opções do compilador, etc.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top