ما هو Object Slicing في C++: اختفاء البيانات الخفي

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

ما هو Object Slicing في C++: شرح كامل مع كود

\n\n

تخيل الموقف: لقد كتبت تسلسلاً هرمياً جميلاً للفئات، وتستخدم تعدد الأشكال (polymorphism)، وكل شيء يعمل بشكل مثالي... ثم يظهر خطأ لا يمكنك الإمساك به. البيانات تختفي ببساطة. الكائن يبدو وكأنه "ينحف" أمام عينيك. هل هذا مألوف؟ مرحباً بك في عالم object slicing (سَبر الكائنات) — أحد أكثر الفخاخ غدراً في لغة C++.

\n\n

في هذه المقالة، سنشرح ما هو object slicing، ولماذا يحدث، وكيفية اكتشافه، والأهم من ذلك، كيفية التعامل معه. ستتعلم ليس فقط النظرية، بل سترى أيضاً أمثلة حية من الكود ستساعدك على تجنب هذه المشكلة في مشاريعك.

\n\n

كيف يعمل الوراثة ولماذا يحدث السَبر

\n\n

أولاً، دعنا نتذكر كيف تدير C++ الذاكرة عند استخدام الوراثة. عندما تنشئ كائناً من فئة مشتقة، فإنه يحتوي على نسخة كاملة من الفئة الأساسية بالإضافة إلى الحقول والطرق الخاصة به.

\n\n
class 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\n

كائن Dog يستهلك ذاكرة أكبر من Animal. وهنا يكمن جذر المشكلة.

\n\n

ماذا يحدث عند Object Slicing

\n\n

Object slicing (سَبر الكائن) هو فقدان البيانات الذي يحدث عندما يتم تعيين كائن من فئة مشتقة لمتغير من الفئة الأساسية بالقيمة (by value). يقوم المترجم "بقص" (سَبر) الجزء الخاص بالفئة المشتقة، تاركاً فقط الجزء الأساسي.

\n\n

مثال كلاسيكي:

\n\n
Dog myDog("Rex", 3, "Labrador");\nAnimal animal = myDog;  // ها هو السَبر!\n\nstd::cout << animal.name << ", " << animal.age << std::endl;\n// animal.breed — غير متاح!\n// animal.sound() ستطبع "Some sound"، وليس "Woof!"
\n\n

ماذا حدث هنا؟ لقد أنشأنا كلباً كاملاً باسم وعمر وسلالة. ولكن عند التعيين animal = myDog، قام المترجم بنسخ الجزء الخاص بـ Animal فقط. اختفى حقل breed دون أثر. والدالة الافتراضية؟ لقد تم "سَبرها" أيضاً — الآن يتم استدعاء طريقة الفئة الأساسية.

\n\n

لماذا هذا خطير جداً؟

\n\n

المشكلة هي أن object slicing ينتهك مبدأ تعدد الأشكال. أنت تعتقد أنك تتعامل مع كلب، ولكن في الواقع أنت تتعامل مع حيوان مجرد. هذا يؤدي إلى:

\n
    \n
  • فقدان البيانات (سلالة الكلب، لون السيارة، طراز الهاتف — كلها تختفي)
  • \n
  • عمل غير صحيح للدوال الافتراضية
  • \n
  • أخطاء يصعب اكتشافها لا يتم التقاطها في مرحلة الترجمة
  • \n
\n\n

يكون السَبر خبيثاً بشكل خاص في الحاويات. انظر إلى هذا الكود:

\n\n
std::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();  // دائماً "Some sound"!\n}
\n\n

تتوقع سماع نباح ومواء، لكنك تحصل فقط على أصوات مجردة. الحاوية std::vector<Animal> تخزن كائنات من النوع Animal فقط، وتقوم بسَبر كل ما هو إضافي.

\n\n

تحليل تفصيلي: كيف يعمل السَبر بالضبط

\n\n

لفهم الآلية، دعنا ننظر إلى أحجام الكائنات:

\n\n
std::cout << "Size of Animal: " << sizeof(Animal) << std::endl;\nstd::cout << "Size of Dog: " << sizeof(Dog) << std::endl;\n\n// الإخراج (يعتمد على المنصة):\n// Size of Animal: 40\n// Size of Dog: 56
\n\n

عندما تكتب Animal animal = dog، يقوم المترجم باستدعاء المنشئ الناسخ (copy constructor) للفئة الأساسية، والذي ينسخ فقط الأعضاء الموجودين في الفئة الأساسية، متجاهلاً تماماً الأعضاء الإضافية من الفئة المشتقة.

المدونات

توصيات الكتب