الاستثناءات (Exceptions) في C++

🎯 مقدمة:

كل برنامج، مهما كان بسيط، لازم يتعامل مع الأخطاء.

  • قراءة ملف غير موجود؟
  • قسمة على صفر؟
  • إدخال خاطئ من المستخدم؟
  • فشل في الاتصال بالخادم؟
  • مساحة الذاكرة غير كافية؟

المبرمج “العادي” يترك البرنامج ينهار.
المبرمج المحترف يبني كود يتحمّل الأخطاء ويعالجها بشكل نظيف…
وهذا بالضبط دور Exceptions.


🔥 أولًا: ما هي الاستثناءات (Exceptions)؟

الاستثناء هو “حدث غير طبيعي” يحصل أثناء تشغيل البرنامج، يؤدي لإيقاف سير البرنامج الطبيعي.

C++ تمكنك من:

✔ اكتشاف الخطأ
✔ إطلاق خطأ
✔ التقاط الخطأ
✔ التعامل معه بدون انهيار البرنامج

الآلية تتم عبر:

  • try
  • throw
  • catch

⚙️ ثانيًا: الفكرة الأساسية — try / catch / throw

📦 الصيغة العامة:

try {
    // كود قد يحدث فيه خطأ
} 
catch (type errorVar) {
    // معالجة الخطأ هنا
}

🔹 throw

يستخدم لإطلاق خطأ (استثناء).

throw "حدث خطأ!";

🔹 try

يحتوي الكود الذي قد يفشل.

🔹 catch

يُستخدم لالتقاط الخطأ.


🧩 مثال بسيط لفهم الاستثناءات

#include <iostream>
using namespace std;

int main() {

    try {
        throw "حدث خطأ في البرنامج!";
    }
    catch (const char* error) {
        cout << "الاستثناء: " << error << endl;
    }
}

📤 النتيجة:

الاستثناء: حدث خطأ في البرنامج!

🚨 ثالثًا: مثال عملي — منع القسمة على صفر

#include <iostream>
using namespace std;

double divide(double a, double b) {
    if (b == 0) {
        throw "خطأ: لا يمكن القسمة على صفر!";
    }
    return a / b;
}

int main() {
    try {
        cout << divide(10, 0);
    }
    catch (const char* msg) {
        cout << msg << endl;
    }
}

🎯 رابعًا: أنواع الأشياء التي يمكنك رميها (throw)

واحدة من أقوى ميزات C++ أنك تستطيع رمي أي شيء تقريبًا:

النوعمثال
نص ثابتthrow "Error";
عددthrow 404;
كائن من Classthrow MyError("…");
Structthrow MyStruct;
Exception standardthrow runtime_error("…");

🔥 خامسًا: catch المتعددة

تستطيع التعامل مع عدة أخطاء بأنواع مختلفة:

try {
    throw 500;
}
catch (int x) {
    cout << "خطأ رقمي: " << x << endl;
}
catch (const char* msg) {
    cout << msg << endl;
}
catch (...) {
    cout << "استثناء غير معروف!" << endl;
}

🔥 catch(…)

تعالج أي خطأ لم يتم التقاطه.


🧠 سادسًا: رمي أخطاء من داخل الدوال

void login(string username) {
    if (username != "admin") {
        throw string("مستخدم غير مصرح!");
    }
}
try {
    login("mohammad");
}
catch (string e) {
    cout << e << endl;
}

📌 سابعًا: رمي كائنات Exceptions جاهزة من المكتبة القياسية

C++ تقدم مكتبة استثناءات جاهزة:

  • exception
  • runtime_error
  • logic_error
  • out_of_range
  • bad_alloc ← يحدث عند فشل new
  • invalid_argument
  • overflow_error

مثال احترافي:

#include <stdexcept>

throw runtime_error("خطأ أثناء التشغيل");

🧨 ثامنًا: معالجة bad_alloc (فشل الذاكرة)

try {
    int* p = new int[1000000000000];
}
catch (bad_alloc& e) {
    cout << "خطأ في تخصيص الذاكرة!" << endl;
}

🧱 تاسعًا: إعادة رمي الاستثناء (rethrow)

لو بدك تلتقط الخطأ وتعطيه لجهة أعلى:

try {
    throw runtime_error("Error!");
}
catch (...) {
    throw; // إعادة رميه
}

🚀 عاشرًا: Exception داخل Class

🧩 في الـ Constructors

class Person {
public:
    Person(int age) {
        if (age < 0)
            throw invalid_argument("العمر لا يمكن أن يكون سالباً");
    }
};

🔥 الحادي عشر: لماذا نستخدم Exceptions بدل return codes؟

الطريقةReturn valuesExceptions
هل ينفصل الخطأ عن المنطق؟
هل يتم اكتشاف الاخطاء بسهولة؟
هل مناسب لمشاريع كبيرة؟
هل يمكن نقل الأخطاء لأعلى؟

الاستثناءات تجعل الكود:
✔ أوضح
✔ أقوى
✔ أكثر أمانًا
✔ قابلًا للصيانة
✔ مهنيًا


⚠️ الثاني عشر: الأخطاء الشائعة في التعامل مع الاستثناءات

❌ 1. وضع try حول الكود كله بدون داعٍ

❌ 2. استخدام catch(…) بدون داع

يجعل تصحيح الأخطاء أصعب.

❌ 3. رمي أنواع بدائية فقط

يفضل دائمًا رمي كائنات.

❌ 4. نسيان const reference

مثال سيء:

catch (runtime_error e) {}

الأصح:

catch (const runtime_error& e) {}

📘 الثالث عشر: مثال واقعي ضخم — نظام حساب درجات مع معالجة أخطاء إدخال

#include <iostream>
#include <stdexcept>
using namespace std;

double readGrade() {
    double g;
    cout << "أدخل العلامة: ";
    cin >> g;

    if (!cin) {
        throw runtime_error("إدخال غير صالح!");
    }

    if (g < 0 || g > 100) {
        throw out_of_range("العلامة يجب أن تكون بين 0 و 100");
    }

    return g;
}

int main() {
    try {
        double grade = readGrade();
        cout << "علامتك هي: " << grade << endl;
    }
    catch (const runtime_error& e) {
        cout << "خطأ إدخال: " << e.what() << endl;
    }
    catch (const out_of_range& e) {
        cout << "خطأ في القيمة: " << e.what() << endl;
    }
    catch (...) {
        cout << "خطأ غير معروف!" << endl;
    }
}

🎯 الرابع عشر: استخدام الاستثناءات مع الملفات

ifstream f("data.txt");

if (!f) {
    throw runtime_error("فشل في فتح الملف!");
}

🌍 الخامس عشر: روابط خارجية موثوقة

🔗 cppreference — Exceptions
https://en.cppreference.com/w/cpp/error/exception

🔗 Programiz — C++ Exceptions
https://www.programiz.com/cpp-programming/exceptions

🔗 GeeksForGeeks — Exception Handling
https://www.geeksforgeeks.org/exception-handling-c/


🧩 الخلاصة النهائية

الاستثناءات مش مجرد try / catch…
هي نظام كامل لبناء برامج قوية تتحمل الأخطاء.

في هذا الدرس تعلمت:

✔ ما هي الاستثناءات ولماذا نحتاجها
✔ الفرق بين try / catch / throw
✔ catch المتعددة
✔ catch(…)
✔ رمي الأخطاء داخل الدوال
✔ رمي Objects بدل نصوص
✔ التعامل مع bad_alloc
✔ الاستثناءات داخل Constructor
✔ أخطاء شائعة وكيف نتجنبها
✔ مثال عملي كبير
✔ التعامل مع الملفات عبر الاستثناءات

هذا المستوى من الفهم يخليك تبني برامج قوية “ما بتوقع” من أول خطأ.


اكتشاف المزيد من كود التطور

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك رد

Scroll to Top