🎯 مقدمة:
C++11 غيّرت قواعد اللعبة.
قبل C++11، إدارة الذاكرة كانت “مجزرة”:
- نسيان
delete - تكرار
deleteلنفس المؤشر - Memory Leak
- Dangling Pointers
- استخدام الذاكرة بعد حذفها
- Ownership غير مفهوم
لكن بعد C++11:
Smart Pointers = الثورة الحقيقية في إدارة الذاكرة.
باختصار:
مؤشرات تدير نفسها بنفسها.
تشبه المؤشرات العادية… لكن تمتلك “عقلًا” يمنعك من ارتكاب الأخطاء الكلاسيكية.
اليوم سنتعلم:
unique_ptrshared_ptr
وسنتعمّق كيف تشتغل داخليًا، ولماذا هي مهمة جدًا.
──────────────────────────────────────────────
⚡ أولًا: لماذا نحتاج Smart Pointers؟
لأن المؤشرات العادية تسبب كوارث:
❌ 1. نسيان delete
يؤدي إلى memory leak
❌ 2. حذف المؤشر مرتين
Double free → crash
❌ 3. مشاركة نفس المؤشر بين أكثر من كائن
Undefined behavior
❌ 4. فقدان المؤشر الأصلي بعد نسخ pointer
كوارث runtime
❌ 5. Ownership غير واضح
مين المسؤول عن delete؟
Smart pointers تحل كل هذا عبر:
✔ إدارة تلقائية للذاكرة
✔ تحديد واضح للملكية
✔ منع التسريبات
✔ سلوك متوقع وآمن
✔ أداء ممتاز (أغلبها “صفري التكلفة”)
──────────────────────────────────────────────
🔥 ثانيًا: ما هو unique_ptr؟
هو مؤشر ذكي يمتلك الذاكرة بشكل حصري.
يعني:
لا يمكن نسخه → فقط يمكن نقله (move)
هذا يمنع نسخ الملكية، ويجعل الذاكرة آمنة جدًا.
📌 طريقة استخدامه:
#include <memory>
using namespace std;
unique_ptr<int> p = make_unique<int>(10);
🧩 الوصول للقيمة:
cout << *p;
🧨 لا يمكنك نسخ unique_ptr:
unique_ptr<int> a = make_unique<int>(5);
unique_ptr<int> b = a; // خطأ!!
لكن يمكنك نقله:
unique_ptr<int> b = move(a);
الآن:
aأصبح فارغًاbأصبح المالك الوحيد للذاكرة
⚡ ثالثًا: مثال عملي كبير
unique_ptr<string> name = make_unique<string>("Mohammad");
cout << *name << endl;
unique_ptr<string> name2 = move(name); // نقل الملكية
بعد النقل:
nameفارغname2تملك القيمة
🧠 لماذا unique_ptr مهم جدًا؟
- يمنع نسخ المؤشر بطريق الخطأ
- يغلق الباب على Memory Leak
- أسرع بكثير من shared_ptr
- الأفضل للوحدات الصغيرة
- يُستخدم في 70% من حالات إدارة الذاكرة الحديثة
🔒 unique_ptr و المصفوفات
auto arr = make_unique<int[]>(5);
arr[0] = 10;
arr[1] = 20;
عند الخروج من النطاق يتم حذف المصفوفة تلقائيًا.
──────────────────────────────────────────────
──────────────────────────────────────────────
🚀 رابعًا: shared_ptr — المؤشر الذكي ذو المرجع المتعدد
وهنا ندخل عالم آخر…
ما هو shared_ptr؟
مؤشر ذكي يسمح لأكثر من كائن أن يمتلك نفس المؤشر.
كيف؟
عن طريق عداد مرجعي (reference count).
- كل مرة تنسخ shared_ptr → يزيد العداد
- كل مرة يخرج shared_ptr من النطاق → ينقص العداد
- عندما يصبح العداد = 0 → تُحذف الذاكرة تلقائيًا
مثال:
shared_ptr<int> p1 = make_shared<int>(100);
shared_ptr<int> p2 = p1; // نسخ مسموح
shared_ptr<int> p3 = p2;
الآن:
count = 3
عندما يخرج p3 من النطاق:
count = 2
وهكذا حتى يصل الصفر.
✨ إنشاء shared_ptr
shared_ptr<int> sp = make_shared<int>(42);
✨ نسخ shared_ptr (مسموح)
shared_ptr<int> a = make_shared<int>(5);
shared_ptr<int> b = a;
shared_ptr<int> c = b;
✨ الوصول للقيمة:
cout << *a;
✨ عدد المراجع:
cout << a.use_count();
⚠️ الشرح الداخلي الخطير:
shared_ptr يحتوي على:
- Pointer عنده data
- Control block يحتوي:
- reference count
- weak count
- deleter
بهذا الشكل:
[shared_ptr] ---> [Control Block] ---> [Object]
🔥 مثال كبير على shared_ptr
#include <iostream>
#include <memory>
using namespace std;
class Person {
public:
string name;
Person(string n) : name(n) {}
};
int main() {
shared_ptr<Person> p1 = make_shared<Person>("Mohammad");
shared_ptr<Person> p2 = p1;
shared_ptr<Person> p3 = p2;
cout << p1.use_count() << endl; // 3
}
──────────────────────────────────────────────
🧨 خامسًا: الفرق بين unique_ptr و shared_ptr
| الخاصية | unique_ptr | shared_ptr |
|---|---|---|
| الملكية | واحدة فقط | متعددة |
| النسخ | ❌ ممنوع | ✔ مسموح |
| النقل move | ✔ مسموح | ❌ غير مهم |
| الذاكرة | تُحذف عند خروج المالك الوحيد | تُحذف عند وصول العداد للصفر |
| السرعة | أسرع | أبطأ بسبب reference count |
| الاستخدام | الموارد الخاصة | مشاركة الموارد |
🎯 متى أستخدم unique_ptr؟
✔ عندما تكون الملكية واضحة
✔ عندما تستخدم كائنًا واحدًا
✔ عندما تطلب أعلى أداء
✔ عندما تريد ضمان عدم النسخ
🎯 متى أستخدم shared_ptr؟
✔ عندما تريد مشاركة نفس الكائن بين عدة كائنات
✔ عندما تعمل على Graphs / Trees
✔ عند التعامل مع APIs معقدة
✔ عندما تحتاج “ملكية مشتركة”
──────────────────────────────────────────────
⚠️ سادسًا: الأخطاء القاتلة مع shared_ptr
❌ 1. إنشاء shared_ptr من نفس المؤشر مرتين
int* p = new int(5);
shared_ptr<int> a(p);
shared_ptr<int> b(p); // خطير جدًا
سينتج double-delete.
⚠ الحل:
دائماً استخدم:
make_shared
❌ 2. استخدام shared_ptr بدل unique_ptr بدون سبب
يُبطّئ البرنامج بدون داعٍ.
❌ 3. حدوث cyclic reference
وهي أخطر مشكلة.
مثال:
- Shared pointer A يمسك B
- Shared pointer B يمسك A
العداد لن يصل للصفر أبدًا → memory leak
الحل:
استخدم weak_ptr
⭐ سابعًا: weak_ptr (شرح سريع مهم)
هو مؤشر غير مالك:
“يشير للكائن بدون ملكيته”.
يُستخدم لتكسير دائرات shared_ptr.
مثال بسيط:
weak_ptr<int> wp = sp; // ما برفع العداد
🎯 ثامنًا: الأمثلة العملية الضخمة
مثال 1: إدارة ذاكرة كائنات كبيرة
unique_ptr<vector<int>> data = make_unique<vector<int>>(1000000);
مثال 2: نظام رسائل يعتمد على shared_ptr
shared_ptr<Message> msg = make_shared<Message>();
queue.push(msg);
log.push(msg);
send.push(msg);
كل الأنظمة تشترك في نفس الرسالة.
مثال 3: Tree Node باستخدام shared_ptr
struct Node {
int val;
shared_ptr<Node> left, right;
};
🔥 تاسعًا: مقارنة شاملة بين Smart Pointers
| النوع | الملكية | السيناريو المثالي |
|---|---|---|
| unique_ptr | ملكية وحيدة | الموارد الخاصة، الكائنات داخل كائنات |
| shared_ptr | ملكية مشتركة | Sharing, events, nodes |
| weak_ptr | عدم ملكية | كسر الدوائر، caching |
🎯 الخلاصة النهائية — Smart Pointers باختصار مفيد:
- المؤشرات الذكية = طريقة آمنة واحترافية لإدارة الذاكرة
- unique_ptr = ملكية وحيدة → سريع، فعال، آمن
- shared_ptr = مرجع مشترك → مناسب للمشاريع الكبيرة
- weak_ptr = لكسر الدوائر
- make_unique / make_shared → أفضل ممارسة
- استخدام المؤشرات الذكية يقلل 95% من أخطاء الذاكرة بالمشاريع
وهذا الدرس هو حجر الأساس لأي مشروع C++ حديث، خصوصًا إذا بدك تبرمج بطريقة clean professional.
اكتشاف المزيد من كود التطور
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.


