C++: معالجة الاستثناءات

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

عند تشغيل البرامج، تحدث دائماً أشياء غير متوقعة: ملفات غير موجودة، نفاد الذاكرة، انقطاع الشبكة...

إذا تُركت دون معالجة، ينهار البرنامج.

معالجة الاستثناءات هي "وسادة الهواء" للبرنامج - عند حدوث مشاكل، يستجيب بلباقة بدلاً من الانهيار.


1. أساسيات معالجة الاستثناءات

(1) 1.1 ما هو الاستثناء؟

الاستثناء هو خطأ أو حالة غير متوقعة تحدث أثناء تنفيذ البرنامج.

استثناءات شائعة:


(2) 1.2 العناصر الثلاثة لمعالجة الاستثناءات

تستخدم معالجة استثناءات C++ ثلاث كلمات مفتاحية:

الكلمة المفتاحية الوظيفة
try مراقبة الكود الذي قد يُلقي أخطاء
catch التقاط ومعالجة الاستثناءات
throw إلقاء استثناء

(3) 1.3 مثال أساسي

مثال: استثناء القسمة على صفر (الصعوبة ⭐)

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

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

int divide(int a, int b) {
 if (b == 0) {
 throw "Divisor cannot be 0!"; // إلقاء استثناء
 }
 return a / b;
}

int main() {
 try {
 std::cout << divide(10, 2) << std::endl; // طبيعي
 std::cout << divide(10, 0) << std::endl; // يُلقي استثناء
 }
 catch (const char* msg) { // التقاط استثناء
 std::cerr << "Error: " << msg << std::endl;
 }
 
 std::cout << "Program continues running" << std::endl;
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
5
Error: Divisor cannot be 0!
Program continues running

نتيجة التنفيذ:

TEXT 📖 للعرض فقط
5
Error: Divisor cannot be 0!
Program continues running

💡 نصيحة:



2. أنواع الاستثناءات

(1) 2.1 الأنواع التي يمكن إلقاؤها

يمكن لـ throw إلقاء أي نوع:

النوع مثال
الأنواع الأساسية throw 42;
النصوص throw "error";
الاستثناءات القياسية throw std::runtime_error("error");
استثناءات مخصصة throw MyException();

(2) 2.2 التقاط استثناءات متعددة

مثال: التقاط أنواع مختلفة من الاستثناءات (الصعوبة ⭐⭐)

CPP
#include <iostream>
### ▶ مثال 2: مثال برمجي (الصعوبة ⭐)

#include <string>

void process(int value) {
 if (value == 0) {
 throw 0; // إلقاء int
 }
 if (value < 0) {
 throw std::string("Negative number"); // إلقاء string
 }
}

int main() {
 try {
 process(-5);
 }
 catch (int e) {
 std::cerr << "Caught int exception: " << e << std::endl;
 }
 catch (const std::string& e) {
 std::cerr << "Caught string exception: " << e << std::endl;
 }
 
 return 0;
}


3. الاستثناءات القياسية

(1) 3.1 هرمية std::exception

تُعرّف مكتبة C++ القياسية مجموعة من فئات الاستثناءات، جميعها في stdexcept:

TEXT 📖 للعرض فقط
std::exception
 ├── std::logic_error
 │ ├── std::invalid_argument
 │ ├── std::out_of_range
 │ └── std::length_error
 └── std::runtime_error
 ├── std::overflow_error
 └── std::underflow_error

(2) 3.2 الاستثناءات القياسية الشائعة

فئة الاستثناء الوصف
std::invalid_argument معامل غير صالح
std::out_of_range خارج الحدود
std::runtime_error خطأ في وقت التنفيذ
std::bad_alloc فشل تخصيص الذاكرة (فشل new)

مثال: استخدام الاستثناءات القياسية (الصعوبة ⭐⭐)

CPP
#include <iostream>
#include <stdexcept>
#include <vector>

int main() {
 std::vector<int> v = {1, 2, 3};
 
 try {
 v.at(10); // وصول خارج الحدود
 }
 catch (const std::out_of_range& e) {
 std::cerr << "Caught exception: " << e.what() << std::endl;
 }
 
 return 0;
}


4. استثناءات مخصصة

(1) 4.1 الوراثة من std::exception

أفضل ممارسة: يجب أن ترث فئات الاستثناءات المخصصة من std::exception.

مثال: فئة استثناء مخصصة (الصعوبة ⭐⭐⭐)

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

// فئة استثناء مخصصة
class MyException : public std::exception {
private:
 std::string msg;
 
public:
 MyException(const std::string& msg) : msg(msg) {}
 
 const char* what() const noexcept override {
 return msg.c_str();
 }
};

int main() {
 try {
 throw MyException("Custom exception");
 }
 catch (const MyException& e) {
 std::cerr << "Caught custom exception: " << e.what() << std::endl;
 }
 
 return 0;
}


5. أمان الاستثناءات

(1) 5.1 ما هو أمان الاستثناءات؟

أمان الاستثناءات يعني أنه حتى عند إلقاء استثناء، يحافظ البرنامج على حالة متسقة (لا تسرب موارد، لا تلف بيانات).

ثلاثة مستويات ضمان:

  1. الضمان الأساسي: حالة البرنامج متسقة، لكن قد تكون قد تغيرت
  2. الضمان القوي: العملية إما تنجح أو تتراجع بالكامل (كأنها لم تحدث)
  3. ضمان عدم الإلقاء: الدالة لا تُلقي استثناء أبداً

(2) 5.2 RAII - أداة فعالة لإدارة الموارد

RAII (Resource Acquisition Is Initialization): إدارة الموارد عبر الكائنات، مع تحريرها تلقائياً في المدمرات.

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

CPP
#include <iostream>
#include <memory>

void process() {
 // استخدام unique_ptr لإدارة الذاكرة؛ حتى لو أُلقي استثناء، ستُحرر تلقائياً
 std::unique_ptr<int> p(new int(42));
 
 // ... كود قد يُلقي استثناءات ...
 
 std::cout << *p << std::endl;
} // p يُحرر تلقائياً

int main() {
 try {
 process();
 }
 catch (...) {
 std::cerr << "Caught exception" << std::endl;
 }
 return 0;
}


6. الكلمة المفتاحية noexcept

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

أدخلت C++11 noexcept، التي تعلن أن دالة لا تُلقي استثناءات.

مثال: الإعلان عن عدم الإلقاء (الصعوبة ⭐)

TEXT 📖 للعرض فقط
void safeFunction() noexcept {
 // هذه الدالة تضمن عدم إلقاء استثناءات
}

void unsafeFunction() {
 // قد تُلقي استثناءات
}

💡 نصيحة:


❓ أسئلة شائعة

س: متى أستخدم الاستثناءات؟ ج: للحالات غير المتوقعة حقاً، وليس لتدفق التحكم العادي.


س: ما هو catch(...)؟ ج: يلتقط جميع الاستثناءات، يُستخدم عادةً لتنظيف الموارد قبل إعادة الإلقاء.

CPP
try {
 // ...
}
catch (...) {
 // تنظيف الموارد
 throw; // إعادة الإلقاء
}

س: هل يمكن للبانيات إلقاء استثناءات؟ ج: نعم، لكن احذر من تسرب الذاكرة. استخدم المؤشرات الذكية أو RAII لإدارة الموارد.


▶ مثال 3: التقاط الاستثناءات (الصعوبة ⭐)

CPP
#include <iostream>
#include <stdexcept>

double divide(double a, double b) {
    if (b == 0) {
        throw std::runtime_error("Divisor cannot be 0");
    }
    return a / b;
}

int main() {
    try {
        std::cout << divide(10, 2) << std::endl;
        std::cout << divide(10, 0) << std::endl;
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
Error: Divisor cannot be 0
💡 نصيحة: try-catch يلتقط الاستثناءات، throw يُلقي الاستثناءات. what() تُرجع رسالة الخطأ.


المفهوم النقطة الأساسية
try-catch-throw العناصر الثلاثة لمعالجة الاستثناءات
الاستثناءات القياسية std::runtime_error وغيرها
استثناءات مخصصة ترث من std::exception
أمان الاستثناءات RAII يضمن عدم تسرب الموارد
noexcept تعلن أن الدالة لا تُلقي استثناءات

📖 ملخص

📝 تمارين

  1. أساسي (الصعوبة ⭐): اكتب دالة قسمة تُلقي std::runtime_error عندما يكون القاسم 0، واستخدم try/catch في main لالتقاط وإخراج رسالة الخطأ.

  2. متوسط (الصعوبة ⭐⭐): عرّف فئة استثناء مخصصة (ترث من std::exception) بحقلين: رمز الخطأ ووصف الخطأ. بعد إلقائها، استرجع كليهما في كتلة catch.

  3. متقدم (الصعوبة ⭐⭐⭐): طبّق فئة "حارس موارد" تكتسب الموارد في الباني وتُحررها في المدمر. حتى لو أُلقي استثناء بينهما، تُحرر الموارد بشكل صحيح (مبدأ RAII).



الدرس التالي: عمليات الملفات المتقدمة (#41)

Web-Tutorial.com

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

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

100%