Kodax

Smart Pointers — المؤشرات والذاكرة

المؤشرات الذكية (Smart Pointers)

مشكلة new/delete الأساسية: يجب أن تتذكر التحرير في كل مسار، حتى عند return المبكر أو الاستثناءات. المؤشرات الذكية (#include <memory>) كائنات تحمل المؤشر وتحرر الذاكرة تلقائياً عندما تنتهي حياتها. هذه الفكرة تسمى RAII: ربط المورد (ذاكرة، ملف، قفل) بحياة كائن على الـ Stack.

unique_ptr: مالك واحد

#include <memory>
auto p = make_unique<int>(42);
cout << *p;

- مالك وحيد: لا يمكن نسخه، لكن يمكن نقل ملكيته: auto q = std::move(p); (بعدها p فارغ).

- يُحرَّر تلقائياً عند خروجه من النطاق.

- بلا تكلفة إضافية تقريباً مقارنة بالمؤشر العادي.

- الخيار الافتراضي لأي ذاكرة ديناميكية.

shared_ptr: ملكية مشتركة

auto a = make_shared<string>("Kodax");
auto b = a;                 // نسخة: الاثنان يملكان
cout << a.use_count();      // 2

يحتفظ بعدّاد للمالكين، ويحرر الذاكرة عندما يصل العدّاد إلى صفر. أبطأ قليلاً من unique_ptr؛ استخدمه فقط عندما تكون الملكية مشتركة فعلاً.

weak_ptr: مراقبة بدون ملكية

يشير إلى كائن يملكه shared_ptr دون زيادة العدّاد. ضروري لكسر الحلقات: إذا كان A يملك B و B يملك A بـ shared_ptr، فلن يصل العدّاد للصفر أبداً (تسرب). اجعل أحد الاتجاهين weak_ptr. للاستخدام: if (auto sp = w.lock()) { ... }.

make_unique و make_shared

استخدمهما بدل new: أوضح، وأكثر أماناً مع الاستثناءات. القاعدة الحديثة: لا new ولا delete في كود التطبيق.

تمرير المؤشرات الذكية للدوال

| الحاجة | المعامل |

|---|---|

| الدالة تستخدم الكائن فقط | T& أو const T& أو T* (ليس المؤشر الذكي) |

| الدالة تأخذ الملكية | unique_ptr<T> بالقيمة، والمستدعي يكتب std::move |

| الدالة تشارك الملكية | shared_ptr<T> |

p.get() يعطي المؤشر الخام للاستخدام المؤقت دون نقل الملكية.

مصفوفات؟

make_unique<int[]>(n) موجود، لكن std::vector أفضل في أغلب الحالات.

تمرين

أنشئ صنفاً بسيطاً يطبع رسالة في الباني والهادم، ثم جرّب: unique_ptr يخرج من النطاق، نقل الملكية بـ move، و shared_ptr بنسختين مع طباعة use_count في كل خطوة.

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

class Resource {
    string name;
public:
    Resource(string n) : name(n) {
        cout << "📦 إنشاء: " << name << endl;
    }
    ~Resource() {
        cout << "🗑️ تدمير: " << name << endl;
    }
    void use() { cout << "استخدام: " << name << endl; }
};

int main() {
    // ===== unique_ptr =====
    cout << "=== unique_ptr ===" << endl;
    {
        unique_ptr<Resource> uptr = make_unique<Resource>("Unique");
        uptr->use();
        
        // unique_ptr<Resource> copy = uptr;  // خطأ! لا يمكن النسخ
        unique_ptr<Resource> moved = move(uptr);  // نقل الملكية
        
        if (uptr == nullptr) {
            cout << "uptr أصبح فارغاً بعد النقل" << endl;
        }
        moved->use();
    }  // يُحرر تلقائياً هنا!
    
    cout << endl;
    
    // ===== shared_ptr =====
    cout << "=== shared_ptr ===" << endl;
    shared_ptr<Resource> sptr1;
    {
        shared_ptr<Resource> sptr2 = make_shared<Resource>("Shared");
        cout << "العداد: " << sptr2.use_count() << endl;  // 1
        
        sptr1 = sptr2;  // مشاركة!
        cout << "العداد بعد المشاركة: " << sptr1.use_count() << endl;  // 2
        
        sptr2->use();
    }  // sptr2 يخرج من النطاق، لكن المورد باقي!
    
    cout << "العداد بعد خروج sptr2: " << sptr1.use_count() << endl;  // 1
    sptr1->use();
    
    sptr1.reset();  // تحرير يدوي
    cout << endl;
    
    // ===== unique_ptr مع مصفوفة =====
    cout << "=== unique_ptr مع مصفوفة ===" << endl;
    unique_ptr<int[]> arr = make_unique<int[]>(5);
    for (int i = 0; i < 5; i++) {
        arr[i] = (i + 1) * 10;
    }
    
    cout << "المصفوفة: ";
    for (int i = 0; i < 5; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    // ===== مقارنة =====
    cout << "\n=== مقارنة ===" << endl;
    cout << "unique_ptr: ملكية واحدة، الأكفأ" << endl;
    cout << "shared_ptr: ملكية مشتركة، مرونة" << endl;
    cout << "weak_ptr: مراقبة بدون ملكية" << endl;
    
    cout << "\n✓ لا حاجة لـ delete!" << endl;
    
    return 0;
}
اختبر فهمك