فصل ۱۱: اشاره‌گرهای هوشمند (Smart Pointers) در C++

""

۱. مقدمه: چرا به اشاره‌گرهای هوشمند نیاز داریم؟

در فصل قبل دیدیم که اگر مدیریت حافظه پویا را خودمان با new و delete انجام بدهیم، احتمال خطاهایی مثل:

  • Memory Leak (نشت حافظه)
  • Dangling Pointer (اشاره به آدرس آزاد شده)
  • Double Delete (آزاد کردن دو بار یک حافظه)
    وجود دارد.

در برنامه‌های بزرگ یا پیچیده، مدیریت دستی حافظه دشوار و خطرناک است. برای حل این مشکل، زبان C++ از نسخه‌ی C++11 به بعد اشاره‌گرهای هوشمند را معرفی کرد.


۲. اشاره‌گر هوشمند چیست؟

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

یعنی:

  • وقتی اشاره‌گر از محدوده (Scope) خارج شود، خودش حافظه را آزاد می‌کند.
  • به مدیریت حافظه هوشمندانه کمک می‌کند.
  • از بسیاری باگ‌ها جلوگیری می‌کند.

کتابخانه <memory> این کلاس‌ها را فراهم می‌کند.


۳. انواع اشاره‌گرهای هوشمند

در STL سه نوع اصلی داریم:

  1. std::unique_ptr – مالکیت یکتا (فقط یک اشاره‌گر می‌تواند مالک حافظه باشد)
  2. std::shared_ptr – مالکیت اشتراکی (چند اشاره‌گر می‌توانند یک حافظه را به اشتراک بگذارند)
  3. std::weak_ptr – اشاره‌گر ضعیف (برای جلوگیری از حلقه‌های مالکیت در shared_ptr)

۴. unique_ptr – مالکیت یکتا

ویژگی‌ها:

  • فقط یک unique_ptr می‌تواند حافظه را مدیریت کند.
  • وقتی از محدوده خارج می‌شود، حافظه را آزاد می‌کند.
  • قابل کپی کردن نیست، اما می‌توان مالکیت را منتقل (move) کرد.

مثال:

#include <iostream>
#include <memory> // برای unique_ptr
using namespace std;

int main() {
    unique_ptr<int> p1(new int(42));

    cout << *p1 << endl;

    // انتقال مالکیت
    unique_ptr<int> p2 = move(p1);

    if (!p1)
        cout << "p1 دیگر مالک حافظه نیست" << endl;

    cout << *p2 << endl;
}

توضیح:

  • new int(42) حافظه می‌گیرد.
  • p1 مالک است.
  • با move(p1) مالکیت به p2 منتقل می‌شود.
  • وقتی p2 از محدوده خارج شود، حافظه آزاد می‌شود.

۵. shared_ptr – مالکیت اشتراکی

ویژگی‌ها:

  • چند shared_ptr می‌توانند به یک حافظه اشاره کنند.
  • حافظه وقتی آزاد می‌شود که آخرین shared_ptr از بین برود.
  • شمارنده‌ی مرجع (Reference Count) دارد.

مثال:

#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<int> sp1(new int(5));
    shared_ptr<int> sp2 = sp1; // اشتراک مالکیت

    cout << "sp1: " << *sp1 << endl;
    cout << "sp2: " << *sp2 << endl;

    cout << "تعداد مالکان: " << sp1.use_count() << endl;
}

خروجی:

sp1: 5
sp2: 5
تعداد مالکان: 2

۶. weak_ptr – اشاره‌گر ضعیف

ویژگی‌ها:

  • به حافظه‌ای که توسط shared_ptr مدیریت می‌شود اشاره می‌کند، اما مالک نیست.
  • شمارنده مرجع را افزایش نمی‌دهد.
  • برای جلوگیری از حلقه‌های بی‌پایان (Circular Reference) در shared_ptr استفاده می‌شود.
  • قبل از استفاده باید بررسی شود که حافظه هنوز معتبر است.

مثال:

#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<int> sp = make_shared<int>(10);
    weak_ptr<int> wp = sp;

    cout << "تعداد مالکان: " << sp.use_count() << endl;

    if (auto temp = wp.lock()) {
        cout << "Value: " << *temp << endl;
    } else {
        cout << "حافظه آزاد شده است" << endl;
    }
}

۷. چرا make_unique و make_shared؟

استفاده از تابع‌های کمکی:

  • امن‌تر هستند.
  • احتمال خطا کمتر است.
  • سریع‌تر و بهینه‌تر مدیریت می‌شوند.

مثال:

auto ptr = make_unique<int>(25);
auto sp = make_shared<string>("Hello");

۸. مثال عملی کامل

مدیریت یک کلاس با اشاره‌گر هوشمند:

#include <iostream>
#include <memory>
using namespace std;

class Person {
public:
    string name;
    Person(string n) : name(n) {
        cout << "ساخت شخص: " << name << endl;
    }
    ~Person() {
        cout << "حذف شخص: " << name << endl;
    }
};

int main() {
    {
        unique_ptr<Person> p1 = make_unique<Person>("Ali");
        cout << p1->name << endl;
    } // اینجا پ1 از محدوده خارج می‌شود و حافظه آزاد می‌گردد

    {
        shared_ptr<Person> sp1 = make_shared<Person>("Sara");
        shared_ptr<Person> sp2 = sp1;

        cout << "تعداد مالکان: " << sp1.use_count() << endl;
    } // وقتی آخرین shared_ptr از بین برود، حافظه آزاد می‌شود
}

۹. نکات و بهترین شیوه‌ها

  • همیشه make_unique و make_shared را به new ترجیح دهید.
  • از unique_ptr برای مالکیت یکتا استفاده کنید؛ سریع‌تر از shared_ptr است.
  • از weak_ptr برای شکستن حلقه‌های مالکیت استفاده کنید.
  • نیازی به delete دستی نیست، چون مدیریت توسط کلاس انجام می‌شود.
  • از اشاره‌گرهای خام (raw pointers) فقط وقتی استفاده کنید که همیشه معلوم است عمر داده دقیقاً توسط دیگری مدیریت می‌شود.

۱۰. تمرین‌ها

  1. یک برنامه بنویسید که یک کلاس ساده با سازنده و مخرب داشته باشد و آن را با unique_ptr مدیریت کند.
  2. برنامه‌ای بسازید که چند shared_ptr به یک کلاس بدهد و تعداد مالکان را چاپ کند.
  3. مثال حلقه مالکیت در shared_ptr ایجاد کنید و با weak_ptr آن را بشکنید.
  4. با make_unique و make_shared چند شیء ایجاد و مقادیر آن‌ها را تغییر دهید.

۱۱. جمع‌بندی

  • اشاره‌گرهای هوشمند ابزار ایمن و مدرن مدیریت حافظه در C++ هستند.
  • با خارج شدن اشاره‌گر از محدوده، حافظه آزاد می‌شود.
  • استفاده از آن‌ها به جای new و delete دستی باعث کم شدن باگ‌ها و مدیریت بهتر منابع می‌شود.
  • در فصل‌های بعد، با کاربردهای این اشاره‌گرها در پروژه‌های واقعی آشنا خواهیم شد.
محمد وب‌سایت

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *