آشنایی با اشاره گر های هوشمند در ++C

در این مقاله به موضوعات زیر پرداخته شده است:

  • اهمیت اشاره گر ها در در C/C++
  • مشکلات اشاره گر های معمولی
  • دلیل ایجاد اشاره گر های هوشمند (Smart Pointers)
  • انواع اشاره گر های هوشمند

اهمیت اشاره گر ها در C و سی پلاس پلاس

تفاوت متغیر های اشاره گر با متغیر های معمولی در این است که آن ها به جای مقدار داده، آدرس متغیر ها و حافظه را در خود ذخیره می کنند. اشاره گر های کاربرد های مختلفی که از آن ها می توان به دسترسی به آدرس متغیر ها و توابع اشاره کرد. با استفاده از این ویژگی می توانیم توابع را به صورت “ارسال با مرجع” (Pass by reference) فراخوانی و همچنین می توان آن ها را به صورت Callback فراخوانی کرد. همچنین می توان در ساخت آرایه های پویا و همچنین ساخت انواع ساختمان های داده مانند لیست پیوندی از آن ها استفاده کرد. در کل شما با دانستن آدرس متغیر ها و سایر ساختار ها، کارهای زیادی می توانید انجام دهید و دستتان باز است.

مشکلات اشاره گر های معمولی

برای درک مشکلات اشاره گر های معمولی، باید با حافظه های stack و heap آشنا باشید.

اجازه دهید با تحلیل کد زیر، یکی از مشکلات اشاره گر های معمولی را پیدا کرده و بهتر متوجه شویم:

در کد بالا یک اشاره گر معمولی به نام p داریم که در تابع ()fun تعریف شده و به یک شئ از کلاس Rectangle اشاره می کند. هنگامی که تابع fun به پایان برسد، اشاره گر p نیز از حافظه حذف می شود زیرا به صورت یک متغیر محلی برای آن تابع تعریف شده است، اما شئ Rectangle که با کلمه کلیدی new ساخته شده است، در واقع در حافظه heap ایجاد شده و با پایان یافتن تابع، از حافظه حذف (deallocate) نمی شود.

در تابع ()main، یک حلقه بی نهایت ایجاد کرده ایم که در هربار اجرا، تابع ()fun را فراخوانی می کند. همانطور که گفتیم، شئی که از کلاس Rectangle در آن تابع ایجاد شده است، با به پایان رسیدن تابع از بین نمی رود. بنابراین در حلقه while که تا زمانی که برنامه بسته نشود، اجرا می شود، در حال ایجاد شئ هستیم. این کار اصطلاحا نشت حافظه (Memory leak) را به همراه دارد. یعنی قسمتی از حافظه را اشغال کرده ایم بدون آن که به آن دسترسی داشته و آن را از حافظه پس بگیریم. کمی پس از اجرای برنامه به دلیل کمبود حافظه، برنامه crash کرده و بسته می شود.

برای جلوگیری از این مشکل باید در انتهای تابع fun از دستور “delete p” استفاده کنیم. این کار باعث می شود تا قسمتی از حافظه که اشاره گر p به آن اشاره دارد، از حافظه بازپس گرفته شود. اما خب ممکن است به دلیل تنبلی یا بی دقتی برنامه نویس، از این دستور استفاده نشده و مشکل به وجود آید. به همین دلیل، C++11 اشاره گر های هوشمند را معرفی کرده و در این مورد به کمک برنامه نویسان آمده و مسئولیت را برعهده می گیرد.

اشاره گر های هوشمند (smart pointers)

در حافظه heap برخلاف حافظه stack، حافظه به طور خودکار مدیریت نمی شود و خود برنامه نویس هنگامی که قسمتی از حافظه را گرفت، خودش باید به حافظه برگرداند (وقتی کارش با آن تمام شد). عواملی مانند بی دقتی و یا نداشتن حوصله در برنامه نویس، موجب بروز مشکل نشت حافظه می شود. در زبان های دیگر مانند #C و Java، مدیریت حافظه بر عهده خود آن ها بوده و توسط مکانیزمی به نام Garbage Collector انجام می شود. C++11 نیز ویژگی جدید به نام smart pointers را معرفی کرده است که به طور خودکار حافظه را مدیریت می کنند. بدین صورت که وقتی اشاره گری از محدوده استفاده خارج می شود، به طور خودکار حافظه شئ مربوط به آن اشاره گر را اصطلاحا deallocate کرده یا به خود حافظه برمی گرداند.

با استفاده از اسمارت پوینتر ها دیگر نیازی نیست تا صریحا از دستور delete برای برگرداندن حافظه استفاده کنیم و بدین صورت حافظه به صورت خودکار مدیریت می شود. اشاره گر هوشمند، چیزی به جز یک کلاس پوشاننده (wrapper) برای یک اشاره گر معمولی نیست. در این کلاس ما عملگر های * و <- را اصطلاحا سربارگذاری عملگر می کنیم. همچنین یک تابع مخرب (destructor) تعریف کرده که در آن از دستور delete برای حذف اشاره گر و محتویاتش از حافظه در هنگامی که کار با اشاره گر به پایان می رسد، استفاده می کنیم.

کد زیر یک کلاس اشاره گر هوشمند را نشان می دهد که می تواند با انواع داده ها (با استفاده از template ها) کار کند.

کد بالا عدد 20 را به عنوان خروجی چاپ می کند.

نکته: smart pointer ها همچنین برای مدیریت منابع مانند فایل ها و سوکت های شبکه نیز مفید هستند.

انواع اشاره گر های هوشمند

در ادامه انواع smart pointer ها را هر کدام کاربرد خاص خود را دارند، معرفی می کنیم:

unique_ptr

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

خروجی:

shared_ptr

اگر از این نوع اشاره گر استفاده کنید، بیش از یک اشاره گر می تواند همزمان به یک شئ اشاره کند. با استفاده از تابع ()use_count می توانید تعداد اشاره گرهایی که به شئ جاری اشاره می کنند را بدست آورید.

خروجی:

 

QR:  آشنایی با اشاره گر های هوشمند در ++C
به اشتراک بگذارید