رفع اشکال Object Slicing در شی گرائی ++C

یکی از چالش هایی که در وراثت با استفاده از زبان پیشرفته سی پلاس پلاس وجود دارد، دسترسی به متدهای کلاس پایه و متدهای override شده در کلاس فرزند با استفاده از یک شی ثابت ویا غیر ثابت از کلاس فرزند هست. جهت واضح شدن این مطلب به مثالهای زیر توجه نمایید. تصور کنید یک کلاس Base داریم ویک کلاس Child که از روی کلاس Base ارث بری کرده است (رابطه 1 به 1) که هم در کلاس پایه و هم کلاس فرزند متد ()getA را پیاده سازی کرده ایم.#include <iostream> #include <vector> #include <functional> class Base { public: Base() : m_a(0) {} virtual void getA(){ std::cout << &quotBase is call&quot << '\n'; } virtual void getA() const{ std::cout << &quotconst Base is call&quot << '\n'; } private: int m_a ; }; class Child : public Base { public: Child() : Base() {} using Base::getA ; void getA() override { std::cout << &quotChild is call&quot << '\n'; } void getA() const { std::cout << &quotconst Child is call&quot << '\n'; } };اکنون با تعریف کلاس های بالا اگر آبجکت هایی به شکل زیر داشته باشیم...std::cout <<&quotObject slicing : &quot << '\n'; Child child2; child2.getA(); //call child method Base& base1ref = child2; //call child method because reference to Child object base1ref.getA(); Base* base1ptr = &child2; //call child method because reference to Child object base1ptr->getA();همانطور که مشاهده میکنید هر سه آبجکت تعریف شده متد ()getA کلاس فرزند را اجرا خواهند کرد.دلیل این اشکال هم این هست که در حال حاضر هر سه شی به کلاس فرزند اشاره دارند در زمان اجرا هیچ راه مستقیمی به کلاس پایه وجود ندارد(البته استثنایی هم دارد قطعا با استفاده از dynamic_cast می توان به کلاس پایه دسترسی داشت ولی منظور استفاده نکردن از explicit cast می باشد). و حال اگر چنین تعاریفی داشته باشیم...Base baseSlice = child2;//call base method because copy the Child object baseSlice.getA();اکنون با تعریف چنین شی به متد های کلاس پایه دسترسی خواهیم داشت که به علت ارسال یک کپی از شی فرزند می باشد که یک نوع از کلاس پایه می باشد(call by value object). Function Slicing اکنون تصور کنید که توابعی را به صورت زیر تعریف کرده ایم(برای اینکه ابهامی در استفاده از توابع رخ نده از overloading function استفاده نشده است)//call base because b is call by value and copy of Base void print(const Base b) { b.getA(); } //call Child because b is call by reference no copy of Base void printref(const Base& b){ b.getA(); } //call Child because b is call by reference by pointer no copy of Base void printptr(const Base* b){ b->getA(); }و در صورتی که این توابع را به شکل زیر استفاده نماییم، توجه نمایید که object slicing چگونه رخ میدهد...std::cout <<&quotfunction slicing : &quot << '\n'; print(child2); printref(child2); printptr(&child2);با توجه به توضیحات قبلی در تابع اول به علت ارسال یک کپی از شی فرزند قطعا به کلاس پایه دسترسی خواهیم داشت. در صورتی که اگر همین شی فرزند را به صورت رفرنس ویا اشاره گر به توابع دوم و سوم ارسال نمایید قطعا فقط به متدهای کلاس فرزند دسترسی خواهیم داشت. Vector Slicing چالش object slicing در زمان استفاده از کانتینر هایی مانند vector بیش از پیش دچار ابهام خواهد بود که در صورت توجه نکردن به این اشکال قطعا در زمان اجرا رفتار برنامه تعریف نشده خواهد بود. تصور کنید که یک کانتینر وکتور به شکل زیر داریم...std::cout <<&quotvector element copy call by value slicing : &quot << '\n'; std::vector<Base> v; v.push_back(Base()); // add a Base object to our vector v.push_back(Child()); // add a Child object to our vector // Print out all of the elements in our vector // all vector element call base because vector element call by value // and copy of Base. for(const auto& element : v){ element.getA(); } v.clear();همانطور که مشاهده میکنید وقتی اشیا وکتور را به صورت مقداری ذخیره میکنید بازهم object slicing خودنمایی خواهد کرد و رفتار برنامه را زیر سوال خواهد برد. اکنون برای رفع این چالش از کانتینر وکتور دو راه حل وجود دارد... استفاده از اشاره گرها به شکل زیر -1std::cout <<&quotvector element not copy call by pointer slicing : &quot << '\n'; std::vector<Base*> vPtr; vPtr.push_back(new Base); vPtr.push_back(new Child); // Print out all of the elements in our vector // all vector element call base because vector element call by value // and copy of Base. for(const auto* element : vPtr){ (*element).getA(); } /*issue-1 * important: can not forget delete element pointer*/ for(const auto* element : vPtr){ delete element; element = nullptr; } vPtr.clear();خوب همانطور که حدس زده اید این تکه برنامه هم طولانی تر شده است و هم کدتکراری boiler plate code استفاده شده است، آن هم به علت اینکه حتما باید فضای استفاده شده در heap را باز پس گیری نماییم که در حلقه دوم این کار انجام شده است. اکنون شاید تصور کنید که میشد از چنین تعریفی برای کانتینر وکتور استقاده میکردیم...std::vector<Base&> vRefWrap;قطعا من هم دوست داشتم چنین چیزی وجود داشت ولی متاسفانه با قوانین که در حوزه تعریف رفرنس ها وجود دارد تعریف چنین وکتور امکان پذیر نمی باشد آنهم به علت اینکه یکی از این قوانین این هست که برای تعریف یک رفرنس حتما در زمان تعریف باید یک رفرنس برای آن در نظر بگیرید قطعا استفاده رفرنس به این شکل در پلی مورفیسم سی پلاس پلاس امکانپذیر نخواهد بود. ولی خبر خوشی که می توانم بدهم این هست که استفاده از رفرنس ها در اشیا پلی مورفیک مانند کانتینرهای وکتور درC++11 با استفاده از کلاس استاندارد std::reference_wrapper امکانپذیر شده است. اگر از سی پلاس پلاس 11 به بعد استفاده می نمایید می توانید چنین کدی بنویسید...std::cout <<&quotvector element not copy call by reference wrapper slicing : &quot << '\n'; std::vector<std::reference_wrapper<Base>> vRefWrap; Base base1; Child child3; vRefWrap.push_back(base1); vRefWrap.push_back(child3); for(const auto& element : vRefWrap){ element.get().getA(); } /* no need delete element*/ vRefWrap.clear();اکنون به راحتی به رفرنس هایی از کلاس فرزند و کلاس پایه دسترسی خواهید داشت و هم خطر استفاده از اشاره گرها را نخواهید داشت و کد بسیار ساده تر و هم قابلیت نگهداری بهتری نسبت به سایر کدهای نوشته شده دارا می باشد.