Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

معالجة الاستثناءات في OOP

جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 81 من 104.

توفر معالجة الاستثناءات في لغة C++ طريقة منظمة لاكتشاف الأخطاء التي تحدث أثناء التشغيل والاستجابة لها. في البرمجة كائنية التوجه، تتكامل الاستثناءات بشكل طبيعي مع التسلسلات الهرمية للفئات، مما يسمح لك بإنشاء أنواع أخطاء ذات مغزى تحمل سياقاً حول ما حدث من خطأ.

تستخدم الآلية الأساسية ثلاث كلمات رئيسية: try و throw و catch. توضع الأكواد التي قد تفشل داخل كتلة try، ويتم الإشارة إلى الأخطاء باستخدام throw، وتقوم كتل catch بمعالجة أنواع استثناءات محددة:

#include <iostream>
#include <stdexcept>

class BankAccount {
    double balance;
public:
    BankAccount(double b) : balance(b) {}
    
    void withdraw(double amount) {
        if (amount > balance) {
            throw std::runtime_error("Insufficient funds");
        }
        balance -= amount;
    }
    
    double getBalance() const { return balance; }
};

int main() {
    BankAccount account(100.0);
    
    try {
        account.withdraw(150.0);
    } catch (const std::runtime_error& e) {
        std::cout << "Error: " << e.what() << "\n";
    }
    
    std::cout << "Balance: " << account.getBalance() << "\n";
}

توفر مكتبة C++ القياسية تسلسلاً هرمياً لفئات الاستثناءات التي تنحدر من std::exception. وتشمل الفئات المشتقة الشائعة std::runtime_error لمشاكل وقت التشغيل و std::logic_error للأخطاء البرمجية. توفر جميع الاستثناءات القياسية طريقة what() تعيد رسالة خطأ.

عندما يتم طرح استثناء (exception)، يتم فك المكدس (stack unwinds) - حيث يتم استدعاء المدمرات (destructors) لجميع الكائنات المحلية بينما يبحث البرنامج عن كتلة catch مطابقة. يضمن ذلك إجراء عملية تنظيف صحيحة حتى عند حدوث أخطاء، وهذا هو السبب في أن RAII ومعالجة الاستثناءات يعملان معاً بشكل جيد للغاية.

challenge icon

التحدي

سهل

لنقم ببناء آلة حاسبة للقسمة الآمنة توضح معالجة الاستثناءات (exception handling) في سياق البرمجة كائنية التوجه. ستقوم بإنشاء فئة Calculator تنفذ عمليات القسمة وتطرح استثناءات ذات مغزى عند حدوث خطأ ما، ثم تعالج تلك الاستثناءات بسلاسة في برنامجك الرئيسي.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • Calculator.h: حدد فئة Calculator التي تؤدي العمليات الحسابية مع معالجة صحيحة للأخطاء.

    يجب أن تحتوي فئة Calculator على طريقة divide(double numerator, double denominator) تعيد نتيجة القسمة. ومع ذلك، إذا كان المقام صفراً، فيجب أن تطرح std::runtime_error مع الرسالة Division by zero. إذا كان البسط أو المقام سالباً، فيجب أن تطرح std::invalid_argument مع الرسالة Negative values not allowed.

    قم أيضاً بتضمين طريقة safeSqrt(double value) تعيد الجذر التربيعي للقيمة. إذا كانت القيمة سالبة، اطرح std::runtime_error مع الرسالة Cannot compute square root of negative number. ستحتاج إلى تضمين <cmath> من أجل دالة sqrt.

  • Calculator.cpp: قم بتنفيذ الطرق المعلن عنها في ملف الترويسة (header file). تأكد من تضمين الترويسات اللازمة (<stdexcept> و <cmath>) والتحقق من الشروط قبل إجراء العمليات.
  • main.cpp: اقرأ ثلاثة مدخلات (كل منها في سطر منفصل):
    1. نوع العملية: إما divide أو sqrt
    2. الرقم الأول (double)
    3. الرقم الثاني (double) — يُستخدم فقط للقسمة، ويتم تجاهله في حالة sqrt

    قم بإنشاء كائن Calculator ونفذ العملية المطلوبة داخل كتلة try. إذا نجحت العملية، اطبع Result: [value]. إذا تم التقاط استثناء، اطبع Error: [message] حيث تأتي الرسالة من طريقة what() الخاصة بالاستثناء.

    بعد كتلة try-catch (بغض النظر عما إذا كان قد حدث استثناء أم لا)، اطبع Calculation complete لإثبات أن البرنامج يستمر في التنفيذ بعد معالجة الاستثناءات.

على سبيل المثال، مع المدخلات divide و 10 و 2:

Result: 5
Calculation complete

مع المدخلات divide و 10 و 0:

Error: Division by zero
Calculation complete

مع المدخلات divide و -5 و 2:

Error: Negative values not allowed
Calculation complete

مع المدخلات sqrt و 16 و 0:

Result: 4
Calculation complete

مع المدخلات sqrt و -9 و 0:

Error: Cannot compute square root of negative number
Calculation complete

لاحظ كيف يتعامل البرنامج بسلاسة مع الأخطاء عن طريق التقاط الاستثناءات وعرض رسائل ذات مغزى، ثم يواصل التنفيذ. يحافظ هذا النمط على نظافة معالجة الأخطاء ويجعل منطقك الرئيسي يركز على المسار الناجح (happy path).

ورقة مرجعية

تستخدم معالجة الاستثناءات (Exception handling) في لغة C++ ثلاث كلمات مفتاحية: try، و throw، و catch. توضع الأكواد التي قد تفشل داخل كتلة try، ويتم الإشارة إلى الأخطاء باستخدام throw، بينما تعالج كتل catch أنواعاً محددة من الاستثناءات:

try {
    // Code that might throw an exception
    account.withdraw(150.0);
} catch (const std::runtime_error& e) {
    std::cout << "Error: " << e.what() << "\n";
}

رمي الاستثناءات (Throwing exceptions) من دوال الفئات (class methods):

void withdraw(double amount) {
    if (amount > balance) {
        throw std::runtime_error("Insufficient funds");
    }
    balance -= amount;
}

توفر مكتبة C++ القياسية فئات للاستثناءات تنحدر من std::exception. وتشمل الأنواع الشائعة ما يلي:

  • std::runtime_error - للمشكلات التي تحدث أثناء وقت التشغيل
  • std::logic_error - للأخطاء البرمجية المنطقية
  • std::invalid_argument - للوسائط غير الصالحة

توفر جميع الاستثناءات القياسية دالة what() التي تعيد رسالة الخطأ.

عند رمي استثناء، يتم فك المكدس (stack unwinds) - حيث يتم استدعاء المدمرات (destructors) لجميع الكائنات المحلية أثناء بحث البرنامج عن كتلة catch مطابقة، مما يضمن التنظيف (cleanup) السليم للموارد.

قم بتضمين <stdexcept> لاستخدام فئات الاستثناءات القياسية.

جرّب بنفسك

#include <iostream>
#include <string>
#include "Calculator.h"

using namespace std;

int main() {
    // قراءة المدخلات
    string operation;
    double num1, num2;
    
    cin >> operation;
    cin >> num1;
    cin >> num2;
    
    // إنشاء كائن Calculator
    Calculator calc;
    
    // TODO: استخدم كتلة try-catch لتنفيذ العملية
    // - إذا كانت العملية "divide"، استدعِ calc.divide(num1, num2)
    // - إذا كانت العملية "sqrt"، استدعِ calc.safeSqrt(num1)
    // - عند النجاح، اطبع "Result: " متبوعة بالنتيجة
    // - التقط الاستثناءات واطبع "Error: " متبوعة برسالة الاستثناء (استخدم what())
    // - بعد try-catch، اطبع دائماً "Calculation complete"
    
    
    return 0;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming