رفتن به مطلب
جامعه‌ی برنامه‌نویسان مُدرن ایران
فرهاد شیری

رفع Object Slicing در وراثت با std::reference_wrapper


پست های پیشنهاد شده

Object Slicing

یکی از چالش هایی که در وراثت با استفاده از زبان پیشرفته سی پلاس پلاس وجود دارد، دسترسی به متدهای کلاس پایه و متدهای 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 << "Base is call" << '\n';
    }
    virtual void getA() const{
        std::cout << "const Base is call" << '\n';
    }
private:
    int m_a ;
};
 
class Child : public Base {
public:
    Child() : Base() {
 
    }
 
    using Base::getA ;
    void getA() override {
        std::cout << "Child is call" << '\n';
    }
 
    void getA() const  {
        std::cout << "const Child is call" << '\n';
    }
};

اکنون با تعریف کلاس های بالا اگر آبجکت هایی به شکل زیر داشته باشیم...

std::cout <<"Object slicing : " << '\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 <<"function slicing : " << '\n';
print(child2);
printref(child2);
printptr(&child2);

با توجه به توضیحات قبلی در تابع اول به علت ارسال یک کپی از شی فرزند قطعا به کلاس پایه دسترسی خواهیم داشت. در صورتی که اگر همین شی فرزند را به صورت رفرنس ویا اشاره گر به توابع دوم و سوم ارسال نمایید قطعا فقط به متدهای کلاس فرزند دسترسی خواهیم داشت.

Vector Slicing
چالش object slicing در زمان استفاده از کانتینر هایی مانند vector بیش از پیش دچار ابهام خواهد بود که در صورت توجه نکردن به این اشکال قطعا در زمان اجرا رفتار برنامه تعریف نشده خواهد بود.
تصور کنید که یک کانتینر وکتور به شکل زیر داریم...

std::cout <<"vector element copy call by value slicing : " << '\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 خودنمایی خواهد کرد و رفتار برنامه را زیر سوال خواهد برد.
اکنون برای رفع این چالش از کانتینر وکتور دو راه حل وجود دارد...
 استفاده از اشاره گرها به شکل زیر -1

std::cout <<"vector element not copy call by pointer slicing : " << '\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 <<"vector element not copy call by reference wrapper slicing : " << '\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();

اکنون به راحتی به رفرنس هایی از کلاس فرزند و کلاس پایه دسترسی خواهید داشت و هم خطر استفاده از اشاره گرها را نخواهید داشت و کد بسیار ساده تر و هم قابلیت نگهداری بهتری نسبت به سایر کدهای نوشته شده دارا می باشد.
امیدوارم موفق باشید.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

مهمان
ارسال پاسخ به این موضوع ...

×   شما در حال چسباندن محتوایی با قالب بندی هستید.   حذف قالب بندی

  تنها استفاده از ۷۵ اموجی مجاز می باشد.

×   لینک شما به صورت اتوماتیک جای گذاری شد.   نمایش به عنوان یک لینک به جای

×   محتوای قبلی شما بازگردانی شد.   پاک کردن محتوای ویرایشگر

×   شما مستقیما نمی توانید تصویر خود را قرار دهید. یا آن را اینجا بارگذاری کنید یا از یک URL قرار دهید.


  • کاربران آنلاین در این صفحه   0 کاربر

    هیچ کاربر عضوی،در حال مشاهده این صفحه نیست.

×
×
  • جدید...