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;
}اختبر فهمك