Qu'est-ce que l'object slicing en C++ : explication complète avec code
\n\nImaginez la situation : vous avez écrit une belle hiérarchie de classes, vous utilisez le polymorphisme, tout fonctionne parfaitement… Et soudain, un bug impossible à attraper. Les données disparaissent tout simplement. L'objet semble « maigrir » sous vos yeux. Ça vous dit quelque chose ? Bienvenue dans le monde du object slicing (trançage d'objets) — l'un des pièges les plus sournois du C++.
\n\nDans cet article, nous allons voir ce qu'est l'object slicing, pourquoi il se produit, comment le détecter et, surtout, comment y remédier. Vous découvrirez non seulement la théorie, mais aussi des exemples de code concrets pour éviter ce problème dans vos projets.
\n\nComment fonctionne l'héritage et pourquoi le slicing apparaît
\n\nPour commencer, rappelons comment le C++ gère la mémoire avec l'héritage. Lorsque vous créez un objet d'une classe dérivée, il contient une copie complète de la classe de base ainsi que ses propres champs et méthodes.
\n\nclass Animal {\npublic:\n std::string nom;\n int age;\n Animal(const std::string& n, int a) : nom(n), age(a) {}\n virtual void son() const { std::cout << "Un son\\n"; }\n};\n\nclass Chien : public Animal {\npublic:\n std::string race;\n Chien(const std::string& n, int a, const std::string& r) \n : Animal(n, a), race(r) {}\n void son() const override { std::cout << "Wouf !\\n"; }\n};\n\nL'objet Chien occupe plus de mémoire que Animal. Et c'est là que se trouve la racine du problème.
Que se passe-t-il lors de l'object slicing
\n\nL'object slicing (trançage d'objet) est une perte de données qui se produit lorsqu'un objet d'une classe dérivée est assigné à une variable de la classe de base par valeur. Le compilateur « coupe » (tranche) la partie spécifique à la classe dérivée, ne laissant que la partie de base.
\n\nExemple classique :
\n\nChien monChien("Rex", 3, "Labrador");\nAnimal animal = monChien; // Voilà le slicing !\n\nstd::cout << animal.nom << ", " << animal.age << std::endl;\n// animal.race — inaccessible !\n// animal.son() affichera "Un son", pas "Wouf !"\n\nQue s'est-il passé ? Nous avons créé un chien complet avec un nom, un âge et une race. Mais lors de l'assignation animal = monChien, le compilateur n'a copié que la partie relative à Animal. Le champ race a disparu sans laisser de trace. Et la fonction virtuelle ? Elle aussi a été « tranchée » — c'est désormais la méthode de la classe de base qui est appelée.
Pourquoi est-ce si dangereux ?
\n\nLe problème est que l'object slicing viole le principe du polymorphisme. Vous pensez travailler avec un chien, mais en réalité vous manipulez un animal abstrait. Cela entraîne :
\n- \n
- Une perte de données (race du chien, couleur de la voiture, modèle du téléphone — tout disparaît) \n
- Un mauvais comportement des fonctions virtuelles \n
- Des bugs difficiles à détecter, qui ne sont pas interceptés à la compilation \n
Le slicing est particulièrement sournois dans les conteneurs. Regardez ce code :
\n\nstd::vector<Animal> zoo;\nzoo.push_back(Chien("Rex", 3, "Labrador"));\nzoo.push_back(Chat("Minou", 2, "Persan"));\n\nfor (const auto& animal : zoo) {\n animal.son(); // Toujours "Un son" !\n}\n\nVous vous attendez à entendre des aboiements et des miaulements, mais vous n'obtenez que des sons abstraits. Le conteneur std::vector<Animal> stocke des objets de type Animal, en tranchant tout ce qui est superflu.
Analyse détaillée : comment fonctionne exactement le slicing
\n\nPour comprendre le mécanisme, regardons la taille des objets :
\n\nstd::cout << "Taille de Animal : " << sizeof(Animal) << std::endl;\nstd::cout << "Taille de Chien : " << sizeof(Chien) << std::endl;\n\n// Affichage (dépend de la plateforme) :\n// Taille de Animal : 40\n// Taille de Chien : 56\n\nQuand vous écrivez Animal animal = chien, le compilateur appelle le constructeur de copie