تعمق في الموادم
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 24 من 104.
إنّ المُهدم (destructor) هو دالة عضو خاصة تعمل تلقائياً عندما يتم تدمير كائن. مهمته الأساسية هي تحرير الموارد التي اكتسبها الكائن خلال فترة حياته، مثل الذاكرة المخصصة ديناميكياً، أو مقابض الملفات، أو اتصالات الشبكة.
يملك المدمّر (destructor) نفس اسم الفئة (class)، مسبوقاً بعلامة التلدة ~. وهو لا يأخذ أي معاملات وليس له نوع إرجاع:
class FileHandler {
std::string filename;
int* buffer;
public:
FileHandler(std::string name, size_t size)
: filename(name), buffer(new int[size]) {
std::cout << "Opening " << filename << "\n";
}
~FileHandler() {
delete[] buffer;
std::cout << "Closing " << filename << "\n";
}
};تُستدعى المُهدمات (Destructors) تلقائياً في هذه الحالات:
- تخرج كائنات الـ Stack عن النطاق
- يتم استدعاء
deleteعلى كائن مخصص في الـ heap - تنتهي دورة حياة كائن مؤقت
void example() {
FileHandler f1("data.txt", 100); // تم استدعاء المنشئ (Constructor)
FileHandler* f2 = new FileHandler("log.txt", 50);
delete f2; // تم استدعاء المدمر (Destructor) لـ f2
} // تم استدعاء المدمر لـ f1 (خرج عن النطاق)على عكس المنشئات (constructors)، يمكن للفئة (class) أن تحتوي على مدمر واحد فقط (one destructor). لا يمكنك تحميله بشكل زائد (overload).
إذا لم تقم بتعريف واحد، يقوم المترجم (compiler) بإنشاء مدمر افتراضي (default destructor) لا يفعل شيئاً خاصاً - فهو ببساطة يقوم بتدمير كل عضو. بالنسبة للفئات (classes) التي تدير الموارد، يجب عليك كتابة المدمر الخاص بك لمنع تسرب الذاكرة (memory leaks).
التحدي
سهللنقم ببناء مدير جلسات يتتبع جلسات المستخدمين النشطة ويوضح كيف تقوم المدمرات (destructors) بتنظيف الموارد تلقائيًا عند تدمير الكائنات — سواء خرجت عن النطاق (out of scope) أو تم حذفها صراحةً.
ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:
Session.h: قم بتعريف فئةSessionتمثل جلسة مستخدم نشطة مع بيانات مخصصة ديناميكيًا. يجب أن تحتوي الفئة على:- أعضاء خاصين (Private members):
username(سلسلة نصية string)، وsessionId(عدد صحيح int)، ومؤشر لمصفوفة أعداد صحيحة يسمىactivityLogيتتبع إجراءات المستخدم، بالإضافة إلىlogSizeلحجم المصفوفة. - منشئ (constructor) يأخذ اسم المستخدم، ومعرف الجلسة، وحجم السجل. يجب أن يخصص مصفوفة سجل النشاط على الـ heap، ويهيئ جميع العناصر إلى 0، ويطبع
"Session <sessionId> started for <username>". - مدمر (destructor) يحرر الذاكرة المخصصة ويطبع
"Session <sessionId> ended for <username>". - دالة
logActivity(int activityCode)تخزن رمز النشاط في الموقع التالي المتاح (تتبع الموقع الحالي داخليًا). - دالة
getActivityCount()تعيد عدد الأنشطة التي تم تسجيلها. - دالة
getUsername()تعيد اسم المستخدم.
- أعضاء خاصين (Private members):
main.cpp: توضيح سلوك المدمر في سيناريوهات مختلفة. اقرأ اسم المستخدم وحجم السجل من المدخلات (كل منهما في سطر منفصل)، ثم:- أنشئ كتلة نطاق (scope block) باستخدام الأقواس المتعرجة
{ }وداخلها أنشئ كائنSessionمخصص على الـ stack باستخدام اسم المستخدم المدخل، ومعرف الجلسة101، وحجم السجل المدخل. قم بتسجيل نشاطين بالرمزين1و2، ثم اطبع"Stack session activities: <count>". عندما تنتهي الكتلة، يجب أن يعمل المدمر تلقائيًا. - بعد الكتلة، اطبع
"Stack session destroyed". - أنشئ كائن
Sessionمخصص على الـ heap باستخدامnewمع اسم المستخدم"Guest"، ومعرف الجلسة202، وحجم السجل3. قم بتسجيل نشاط واحد بالرمز5، واطبع"Heap session activities: <count>"، ثم قم بحذفه صراحةً باستخدامdelete. - بعد الحذف، اطبع
"Heap session destroyed".
- أنشئ كتلة نطاق (scope block) باستخدام الأقواس المتعرجة
يوضح هذا التحدي الطريقتين الرئيسيتين لاستدعاء المدمرات: تلقائيًا عندما تخرج كائنات الـ stack عن النطاق، ويدويًا عندما تقوم بحذف كائنات الـ heap. يجب أن يحرر كلا المسارين سجل النشاط المخصص ديناميكيًا بشكل صحيح لمنع تسرب الذاكرة (memory leaks).
قم بتضمين ملف الرأس الخاص بك في main.cpp باستخدام #include "Session.h".
ورقة مرجعية
الـ destructor (أو الهادم) هو دالة عضو خاصة تعمل تلقائيًا عند تدمير الكائن. يقوم بتحرير الموارد مثل الذاكرة المخصصة ديناميكيًا، أو مقابض الملفات، أو اتصالات الشبكة.
يمتلك الـ destructor نفس اسم الفئة (class)، مسبوقًا بعلامة المد ~. لا يأخذ أي معاملات وليس له نوع إرجاع:
class FileHandler {
std::string filename;
int* buffer;
public:
FileHandler(std::string name, size_t size)
: filename(name), buffer(new int[size]) {
std::cout << "Opening " << filename << "\n";
}
~FileHandler() {
delete[] buffer;
std::cout << "Closing " << filename << "\n";
}
};يتم استدعاء الـ destructors تلقائيًا عندما:
- تخرج كائنات المكدس (Stack objects) عن النطاق
- يتم استدعاء
deleteعلى كائن مخصص في الذاكرة الحرة (heap) - تنتهي دورة حياة كائن مؤقت
void example() {
FileHandler f1("data.txt", 100); // تم استدعاء المنشئ (Constructor)
FileHandler* f2 = new FileHandler("log.txt", 50);
delete f2; // تم استدعاء الـ destructor لـ f2
} // تم استدعاء الـ destructor لـ f1 (خرج عن النطاق)يمكن للفئة أن تمتلك destructor واحد فقط (لا يوجد تحميل زائد). إذا لم تقم بتعريف واحد، فسيقوم المترجم بإنشاء destructor افتراضي. بالنسبة للفئات التي تدير الموارد، يجب عليك كتابة الـ destructor الخاص بك لمنع تسرب الذاكرة.
جرّب بنفسك
#include <iostream>
#include <string>
#include "Session.h"
using namespace std;
int main() {
// قراءة المدخلات
string username;
int logSize;
getline(cin, username);
cin >> logSize;
// TODO: قم بإنشاء كتلة نطاق (scope block) باستخدام الأقواس المتعرجة { }
// داخل الكتلة:
// - قم بإنشاء Session مخصص على الـ stack باستخدام username المدخل، و session ID بقيمة 101، وحجم السجل (log size) المدخل
// - قم بتسجيل نشاطين بالرمزين 1 و 2
// - اطبع "Stack session activities: <count>"
// عندما تنتهي الكتلة، يتم تشغيل الـ destructor تلقائياً
// TODO: بعد الكتلة، اطبع "Stack session destroyed"
// TODO: قم بإنشاء Session مخصص على الـ heap باستخدام new
// - Username: "Guest"، و session ID: 202، و log size: 3
// - قم بتسجيل نشاط واحد بالرمز 5
// - اطبع "Heap session activities: <count>"
// - احذف الـ session
// TODO: بعد الحذف، اطبع "Heap session destroyed"
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة