O que é object slicing em C++: explicação completa com código
\n\nImagine a situação: você escreveu uma bela hierarquia de classes, usa polimorfismo, tudo funciona perfeitamente… E então um bug impossível de capturar. Os dados simplesmente desaparecem. O objeto como que "emagrece" diante dos seus olhos. Familiar? Bem-vindo ao mundo do object slicing (fatiamento de objetos) — uma das armadilhas mais traiçoeiras do C++.
\n\nNeste artigo, vamos analisar o que é object slicing, por que ele ocorre, como detectá-lo e, o mais importante, como lidar com ele. Você aprenderá não apenas a teoria, mas também verá exemplos de código ao vivo que o ajudarão a evitar esse problema em seus projetos.
\n\nComo funciona a herança e por que ocorre o fatiamento
\n\nPara começar, vamos lembrar como o C++ gerencia a memória na herança. Quando você cria um objeto de uma classe derivada, ele contém uma cópia completa da classe base, além de seus próprios campos e métodos.
\n\nclass Animal {\npublic:\n std::string name;\n int age;\n Animal(const std::string& n, int a) : name(n), age(a) {}\n virtual void sound() const { std::cout << "Some sound\\n"; }\n};\n\nclass Dog : public Animal {\npublic:\n std::string breed;\n Dog(const std::string& n, int a, const std::string& b) \n : Animal(n, a), breed(b) {}\n void sound() const override { std::cout << "Woof!\\n"; }\n};\n\nO objeto Dog ocupa mais memória que o Animal. E é aí que está a raiz do problema.
O que acontece durante o object slicing
\n\nObject slicing (fatiamento de objeto) é a perda de dados que ocorre quando um objeto de uma classe derivada é atribuído a uma variável da classe base por valor. O compilador "corta" (fatia) a parte específica da classe derivada, deixando apenas a parte da classe base.
\n\nExemplo clássico:
\n\nDog myDog("Rex", 3, "Labrador");\nAnimal animal = myDog; // Aqui está o slicing!\n\nstd::cout << animal.name << ", " << animal.age << std::endl;\n// animal.breed — inacessível!\n// animal.sound() exibirá "Some sound", não "Woof!"\n\nO que aconteceu aqui? Criamos um cachorro completo com nome, idade e raça. Mas ao atribuir animal = myDog, o compilador copiou apenas a parte que pertence a Animal. O campo breed desapareceu sem deixar vestígios. E a função virtual? Ela também foi "fatiada" — agora o método da classe base é chamado.
Por que isso é tão perigoso?
\n\nO problema é que o object slicing viola o princípio do polimorfismo. Você pensa que está trabalhando com um cachorro, mas na verdade está com um animal abstrato. Isso leva a:
\n- \n
- Perda de dados (raça do cachorro, cor do carro, modelo do telefone — tudo desaparece) \n
- Funcionamento incorreto de funções virtuais \n
- Bugs difíceis de detectar que não são capturados em tempo de compilação \n
O slicing é particularmente traiçoeiro em contêineres. Veja este código:
\n\nstd::vector<Animal> zoo;\nzoo.push_back(Dog("Rex", 3, "Labrador"));\nzoo.push_back(Cat("Whiskers", 2, "Persian"));\n\nfor (const auto& animal : zoo) {\n animal.sound(); // Sempre "Some sound"!\n}\n\nVocê espera ouvir latidos e miados, mas obtém apenas sons abstratos. O contêiner std::vector<Animal> armazena objetos exatamente do tipo Animal, fatiando todo o resto.
Análise detalhada: como o slicing funciona exatamente
\n\nPara entender a mecânica, vamos observar os tamanhos dos objetos:
\n\nstd::cout << "Size of Animal: " << sizeof(Animal) << std::endl;\nstd::cout << "Size of Dog: " << sizeof(Dog) << std::endl;\n\n// Saída (depende da plataforma):\n// Size of Animal: 40\n// Size of Dog: 56\n\nQuando você escreve Animal animal = dog, o compilador chama o construtor de cópia (ou operador de atribuição) da classe base, que copia apenas os membros da classe base, ignorando os membros adicionais da classe derivada.