تکین وب | آموزش برنامه نویسی
تکین وب | آموزش برنامه نویسی

Smart pointer چیست؟

۱ مرداد ۱۳۹۵

شاید تا به حال اسم اشاره گرهای هوشمند یا Smart pointerها به گوشتان خورده باشد اما نحوه ی کار کردن و کاربرد آنها را ندانید . در این بخش ضمن معرفی این اشاره گرها ، به پیاده سازی و نحوه ی کارکرد آنها خواهیم پرداخت .

معرفی :

در نگاه اول باید بدانیم چرا باید از این نوع اشاره گر استفاده کنیم و یا اصلا دانستن اینکه این اشاره گرها چیستند و کاربرد آنها چیست ، در برنامه چه مزیتی دارد ؟

گاهی به هنگام کار کردن با اشاره گر ها در برنامه هایی که خیلی طولانی هستند ، مدیریت آن را کمی دشوار می کند . مثلا  فراموش می کنیم که حافظه ی گرفته شده را آزاد کنیم . بنابراین ممکن است برنامه ای که حجم ۵۰ مگ از حافظه را اشغال می کند ، حجم بیشتری مثلا ۷۰۰ مگ را به خود بگیرد . آنگاه شما باید خط به خط برنامه را برای حل این مشکل چک کنید  و وقت زیادی را برای فهمیدن این موضوع  صرف کنید  .

 به تکه کد زیر دقت کنید :

char* pName = new char[1024];
…
SetName(pName);
…
…
if(null != pName)
{
delete[] pName;
}

می دانیم که هنگام استفاده از Poiter ها باید این حافظه آزاد شود .  تصور کنید حافظه ی گرفته شده توسط اشاره گر pName گرفته نشود!!!

حال به کلاس Person و نحوه ی استفاده از آن دقت کنید :

class Person
{
    int age;
    char* pName;

    public:
        Person(): pName(0),age(0)
        {
        }
        Person(char* pName, int age): pName(pName), age(age)
        {
        }
        ~Person()
        {
        }

        void Display()
       {
       std::cout<<"Name = "<<pName <<std::endl <<"Age = "<<age ;
       }
};

و بعد از تعریف کلاس ، به تعریف main می پردازیم :

void main()
{
    Person* pPerson  = new Person("Scott", 25);
    pPerson->Display();
    delete pPerson;
}

در کد بالا باید مواظب باشیم که به هنگام ساخت یک اشاره گر حافظه ی آن  را آزاد کنیم. این همان کاری است که می خواهیم از انجام دادن آن اجتناب کنیم . در واقع ما به یک مکانیزم خودکار جهت delete کردن آن نیازمندیم . چیزی شبیه DESTRUCTOR !! اما pointer ها destructor ندارند ، به همین منظور اشاره گرهای هوشمند ایجاد می شوند .

کلاس Smart Pointer  :

برای شروع باید  کلاسی به نام SP را به صورت زیر تعریف کنیم که می تواند شیءای از کلاس Person را به عنوان ورودی به constructor خود بفرستد .

class SP
{
private:
    Person*    pData; // pointer to person class
public:
    SP(Person* pValue) : pData(pValue)
    {
    }
    ~SP()
    {
        // pointer no longer requried
        delete pData;
    }

    Person& operator* ()
    {
        return *pData;
    }

    Person* operator-> ()
    {    
        return pData;
    }
};

این کلاس همان کلاس اشاره گر هوشمند ماست و می توان main قبلی را به شکل زیر تغییر داد :

void main()
{
    SP<Person> p(new Person("Scott", 25));
    p->Display();
    // Dont need to delete Person pointer..
}   

اما ما نمی خواهیم تنها در یک کلاس مانند کلاس Person از آن استفاده کنیم و می خواهیم کلاس اشاره گر هوشمند ما به گونه ای باشد که برای هر نوعی کار کند . به همین منظور کد بالا را به یک کد عمومی تر تبدیل می کنیم ( که این کار به وسیله ی ایجاد یک قالب یا templete  انجام میگیرد ) :

 

template < typename T > class SP
{
 private:
 T* pData; // Generic pointer to be stored
 public:
 SP(T* pValue) : pData(pValue)
 {
 }
 ~SP()
 {
 delete pData;
 }

 T& operator* ()
 {
 return *pData;
 }

 T* operator-> ()
 {
 return pData;
 }
};

 

چون باید رفتار کلاسی که تعریف می کنیم کاملا شبیه یک Poiter باشد ، لذا باید دو اپراتور * و <- را پیاده سازی کنیم .

اما آیا این واقعا یک کلاس برای اشاره گر  هوشمند است ؟

بررسی یک شرایط خاص :

شرایط خاص که ممکن است باعث کرش شدن برنامه شود ، حالتی به نام اشاره گر آویزان را ایجاد میکند . با توجه به کلاس بالا main زیر را بررسی می کنیم تا مشکل جدیدی که ایجاد میشود را بررسی کنیم :

void main()
{
 SP<Person> p(new Person("Scott", 25));
 p->Display();
 {
 SP<Person> q = p;
 q->Display();
 // Destructor of Q will be called here..
 }
 p->Display();
 std::cout<<std::endl;
 system ("pause");
}

مشکلی که ایجاد شده است ، اشاره گر جدیدی به نام q می باشد . این اشاره گر در یک اسکوپ تعریف شده است و بعد از تعریف آن می خواهیم که به اشاره گر P که خارج از این اسکوپ تعریف شده است ، اشاره کند . اما بعد از صدا زده شدن destructor این اشاره گر ، فضایی که q به آن اشاره می کند آزاد می شود . این همان فضایی است که همزمان اشاره گر p هم به آن اشاره میکند . یعنی بعد از اینکه q آزاد شد ، باید P هم آزاد شود . حال آنکه P دارد به جایی اشاره میکند که دیگر وجود ندارد !! درواقع معلق مانده است !!!

برای اینکه این مشکل را حل کنیم از یک کلاس کمکی دیگر استفاده میکنیم . این کلاس ، کلاس  RC که مخفف Reference Counting است که به صورت زیر دارای دو تابع که مقدار متغیر خصوصی در آنها کم و زیاد می شود ،می باشد :

class RC
{
    private:
    int count; // Reference count

    public:
    void AddRef()
    {
        // Increment the reference count
        count++;
    }

    int Release()
    {
        // Decrement the reference count and
        // return the reference count.
        return --count;
    }
};

پیاده سازی دوباره ی کلاس SP شاید در نگاه اول کمی پیچیده باشد اما برای رفع این مشکل و تمرین در خصوص کد زنی شیء گرایی ++c خوب است . چون کد کمی زیاد به نظر میرسد می توانید آن را از این قسمت  مشاهده نمایید .

با مقایسه ی این کد و سپس اجرای main زیر ، می توان مشکل را حل کرد :

void main()
{
    SP<Person> p(new Person("Scott", 25));
    p->Display();
    {
        SP<Person> q = p;
        q->Display();
        

        SP<Person> r;
        r = p;
        r->Display();
        // Destructor of r will be called here..
        // Destructor of q will be called here..
    }
    p->Display();
    }// Destructor of p will be called here 
     // and person pointer will be deleted 

زمانیکه یک اشاره گر هوشمند P از نوع کلاس Person ایجاد می شود ، constructor کلاس SP فراخوانی می شود . سپس بعد از ذخیره شدن اطلاعات ، اشاره گر RC ایجادخواهد شد . یا فراخوانی سازنده ی کلاس RC ، متد AddRef برای افزودن یک واحد و تغییر count به ۱ وارد می شود .چرا که با ساخت  اشاره گر q ، سازنده ای برای کپی کردن اطلاعات قبلی صدا زده می شود .(توجه کنید که کلاس SP دارای ۳ constructor می باشد که یکی ازز آنها وظیفه ی کپی کردن را به عهده دارد و تنها در آن متد AddRef فراخوانی می شود) در اینصورت با فراخوانی دوباره ی  متد AddRef ، مقدار count از ۱ به ۲ تغییر خواهد کرد . در این اسکوپ بعد از ساخته شدن اشاره گر r ، برای عمل r=p ، اپراتور تساوی پیاده سازی شده است . در اینصورت مقدار count از ۲ به ۳ تغییر می کند .

حال آنکه با بیرون رفتن از این اسکوپ ، دو شیء ایجاد شده نابود خواهند شد و جایی است که reference count کاهش می یابد . اما اطلاعات موجود از بین نخواهند رفت تا زمانیکه مقدار count به صفر برسد و این شرایط یعنی صفر شدن count زمانی اتفاق می افتد که destructor اشاره گر p فراخوانی شود . در این صورت برنامه بدون کرش شدن به خوبی عمل خواهد کرد .

debug کردن برنامه :

شاید مطلب کمی سنگین و پیچیده باشد و نتوانید آن را به خوبی تحلیل کنید . اما پیشنهادی که برای درک این موضوع به شما می کنیم ، این است که همراه با این توضیحات به debug کردن برنامه بپردازید . یعنی خودتان این فراخوانی ها را بررسی کنید . برای این کار با فشار دادن همزمان دکمه های fn و f11 برنامه شروع به debug کردن می کند سپس با فشار دادن مکرر کلیدهای fn و f10 به طور همزمان ، می توان اجرای خط به خط کدهای برنامه را مشاهده کرد . (یعنی هر بار که این دو کلید فشارداده میشوند ، کامپایلر برای عمل کامپایل به سراغ دستور بعدی می رود) یک فلش زرد رنگ در کنار کدهای برنامه برای مشخص کردن اینکه هم اکنون در کدام قسمت از کد هستیم ، نمایان می شود و در قسمت پایین برنامه هم می توانید خط به خط این اجراها را مشاهده کنید .

Screenshot (153)

در نهایت می توانید این کلاس را به همراه main آن از اینجا دانلود نمایید .

در ضمن اگر اینستاگرامی هستید حتما @Takinweb را فالو کنید….

درباره ی عطیه رحمانی

دیدگاه ها

دیدگاه خود را به ما بگویید.

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