C++: المؤشرات الذكية المتقدمة

آخر تحديث: 2026-08-26

في الدرس 45، تعلمنا عن دلالات النقل.

الآن، سنتعمق في المؤشرات الذكية — الأداة الأساسية لإدارة الذاكرة في C++ الحديثة.

مع المؤشرات الذكية، لن تقلق بشأن تسرب الذاكرة بعد الآن.


1. نظرة عامة على المؤشرات الذكية

(1) 1.1 ما هي المؤشرات الذكية؟

المؤشرات الذكية هي أصناف قوالب تدير الذاكرة الديناميكية، وتحرر الذاكرة تلقائيًا (RAII).

ثلاثة أنواع من المؤشرات الذكية:

المؤشر الذكي الوظيفة حالة الاستخدام
unique_ptr الملكية الحصرية الملكية الفردية لكائن
shared_ptr الملكية المشتركة أماكن متعددة تحتاج الوصول لنفس الكائن
weak_ptr مرجع ضعيف كسر المراجع الدائرية


2. unique_ptr

(1) 2.1 الاستخدام الأساسي

unique_ptr هو مؤشر ذكي بـ ملكية حصرية — لا يمكن نسخه، فقط نقله.

مثال: الاستخدام الأساسي لـ unique_ptr (الصعوبة ⭐)

▶ مثال 2: مثال على الكود (الصعوبة ⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <memory>

int main() {
 std::unique_ptr<int> p1(new int(42));
 std::cout << *p1 << std::endl; // Output: 42
 
 // std::unique_ptr<int> p2 = p1; // ❌ خطأ! لا يمكن النسخ
 std::unique_ptr<int> p2 = std::move(p1); // ✅ يمكن النقل
 std::cout << *p2 << std::endl; // Output: 42
 
 return 0;
} // p2 يحرر الذاكرة تلقائيًا

الخرج:

TEXT 📖 للعرض فقط
(خرج البرنامج)

(2) 2.2 المحذف المخصص

مثال: استخدام unique_ptr لإدارة ملف (الصعوبة ⭐⭐)

CPP
#include <iostream>
#include <memory>
#include <cstdio>

// محذف مخصص: إغلاق الملف
struct FileDeleter {
 void operator()(FILE* fp) const {
 if (fp) {
 fclose(fp);
 std::cout << "File closed" << std::endl;
 }
 }
};

int main() {
 std::unique_ptr<FILE, FileDeleter> file(fopen("test.txt", "w"));
 // لا حاجة لإغلاق الملف يدويًا، unique_ptr يستدعي FileDeleter تلقائيًا
 
 return 0;
}


3. shared_ptr

(1) 3.1 الاستخدام الأساسي

shared_ptr هو مؤشر ذكي بـ ملكية مشتركة، يدير الذاكرة بـ عدد المراجع.

مثال: الاستخدام الأساسي لـ shared_ptr (الصعوبة ⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <memory>

int main() {
 std::shared_ptr<int> p1 = std::make_shared<int>(42);
 std::cout << "Reference count: " << p1.use_count() << std::endl; // 1
 
 {
 std::shared_ptr<int> p2 = p1; // نسخ، عدد المراجع +1
 std::cout << "Reference count: " << p1.use_count() << std::endl; // 2
 } // p2 يتدمر، عدد المراجع -1
 
 std::cout << "Reference count: " << p1.use_count() << std::endl; // 1
 
 return 0;
}

(2) 3.2 make_shared مقابل new

موصى به: استخدم std::make_shared بدلاً من new.

المقارنة new make_shared
أمان الاستثناءات قد يتسرب آمن
الأداء تخصيصان تخصيص واحد
وضوح الكود مطوّل موجز
CPP
// موصى به
auto p1 = std::make_shared<int>(42);

// غير موصى به
std::shared_ptr<int> p2(new int(42));


4. weak_ptr

(1) 4.1 لماذا نحتاج weak_ptr؟

المشكلة: shared_ptr قد يسبب مراجع دائرية، مما يؤدي إلى تسرب الذاكرة.

مثال: مرجع دائري (الصعوبة ⭐⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <memory>

struct Node {
 std::shared_ptr<Node> next; // مرجع دائري!
 ~Node() { std::cout << "Node destroyed" << std::endl; }
};

int main() {
 auto n1 = std::make_shared<Node>();
 auto n2 = std::make_shared<Node>();
 
 n1->next = n2; // مرجع دائري
 n2->next = n1;
 
 return 0;
} // ❌ n1 وn2 لن يتم تحريرهما (تسرب ذاكرة)

(2) 4.2 كسر المراجع الدائرية باستخدام weak_ptr

الحل: استبدل أحد shared_ptr بـ weak_ptr.

CPP
struct Node {
 std::weak_ptr<Node> next; // استخدام weak_ptr، لا يزيد عدد المراجع
 ~Node() { std::cout << "Node destroyed" << std::endl; }
};

int main() {
 auto n1 = std::make_shared<Node>();
 auto n2 = std::make_shared<Node>();
 
 n1->next = n2; // لا يزيد عدد المراجع
 n2->next = n1;
 
 return 0;
} // ✅ تم التحرير بشكل صحيح


5. دليل اختيار المؤشرات الذكية

(1) 5.1 كيف تختار؟

السيناريو التوصية
ملكية حصرية unique_ptr
ملكية مشتركة shared_ptr
مراقب weak_ptr أو مؤشر خام
مصفوفات unique_ptr<T>

(2) 5.2 ما لا يجب فعله

الخطأ التوضيح
لا تهيئ عدة shared_ptr من نفس المؤشر الخام سيسبب تحريرًا مزدوجًا
لا تخلط المؤشرات الخام والمؤشرات الذكية يكسر RAII
لا تستدعِ delete يدويًا دع المؤشرات الذكية تدير ذلك


6. تمرين: إدارة الموارد بالمؤشرات الذكية

▶ مثال 1: استخدام المؤشرات الذكية لإدارة اتصال قاعدة بيانات (الصعوبة ⭐⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <memory>

// محاكاة اتصال قاعدة بيانات
class DatabaseConnection {
public:
 DatabaseConnection() {
 std::cout << "Connecting to database" << std::endl;
 }
 
 ~DatabaseConnection() {
 std::cout << "Disconnected" << std::endl;
 }
 
 void query(const std::string& sql) {
 std::cout << "Executing SQL: " << sql << std::endl;
 }
};

int main() {
 // استخدام unique_ptr لإدارة المورد الحصري
 std::unique_ptrDatabaseConnection conn(new DatabaseConnection());
 conn->query("SELECT * FROM users");
 
 return 0;
} // يقطع الاتصال تلقائيًا

الخرج:

TEXT 📖 للعرض فقط
Connecting to database
Disconnected
Executing SQL: 

❓ أسئلة شائعة

س أيهما أسرع، unique_ptr أم shared_ptr؟
ج unique_ptr أسرع (لا يوجد عبء عد المراجع). يُفضّل unique_ptr.

س متى يجب استخدام المؤشرات الخام؟
ج - عندما لا تملك المورد (مراقب) - عند التفاعل مع مكتبات C - عندما يكون الأداء حرجًا للغاية (لكن عادةً غير ضروري)

س: هل shared_ptr آمن للخيوط؟ ج: - تعديلات عدد المراجع آمنة للخيوط - لكن الكائن المُشار إليه ليس آمنًا للخيوط (يحتاج حماية mutex)


▶ مثال 3: الملكية المشتركة مع shared_ptr (الصعوبة ⭐)

CPP
#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Construct" << std::endl; }
    ~Resource() { std::cout << "Destruct" << std::endl; }
};

int main() {
    {
        std::shared_ptr<Resource> p1 = std::make_shared<Resource>();
        std::shared_ptr<Resource> p2 = p1; // ملكية مشتركة

        std::cout << "Reference count: " << p1.use_count() << std::endl;
    }
    // يغادر النطاق، يتدمر تلقائيًا

    std::cout << "Program ended" << std::endl;
    return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
Construct
Destruct
Reference count: 
Program ended
💡 نصيحة: shared_ptr يشارك الملكية ويحرر تلقائيًا عندما يصل عدد المراجع إلى الصفر. استخدام make_shared أكثر أمانًا للإنشاء.


النقطة الرئيسية الملخص
unique_ptr ملكية حصرية، لا يمكن نسخه
shared_ptr ملكية مشتركة، عد المراجع
weak_ptr مرجع ضعيف، يكسر المراجع الدائرية
make_shared الطريقة الموصى بها، آمنة للاستثناءات
مبدأ الاختيار يُفضّل unique_ptr، استخدم shared_ptr عند الحاجة

📖 ملخص

📝 تمارين

  1. أساسي (الصعوبة ⭐): استخدم unique_ptr لإدارة int مُخصص ديناميكيًا، أُنشئ بـ make_unique. حاول نسخ unique_ptr (سينفشل التجميع)، ثم استخدم move بدلاً من ذلك.

  2. متوسط (الصعوبة ⭐⭐): استخدم shared_ptr لتنفيذ كائنات متعددة تشترك في نفس المورد. أنشئ اثنين من shared_ptr يشيران إلى نفس الكائن، واخرج use_count() لمراقبة تغيرات عدد المراجع.

  3. متقدم (الصعوبة ⭐⭐⭐): استخدم weak_ptr لحل مشكلة المرجع الدائري في shared_ptr. أنشئ صنفين A وB يحملان shared_ptr لبعضهما، ولاحظ تسرب الذاكرة. ثم انتقل إلى weak_ptr وتحقق من التحرير الصحيح.



الدرس التالي: مقدمة في برمجة القوالب الفوقية (#47)

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%