معالجة الاستثناءات في OOP
جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 81 من 104.
توفّر معالجة الاستثناءات في C++ طريقةً منظّمة لاكتشاف أخطاء وقت التشغيل والاستجابة لها. في البرمجة كائنية التوجّه، تتكامل الاستثناءات طبيعيًا مع تسلسلات class، مما يتيح لك إنشاء أنواع أخطاء ذات معنى تحمل سياقًا حول ما حدث من خطأ.
تستخدم الآلية الأساسية ثلاث كلمات مفتاحية: 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++ القياسية تسلسلاً هرميًا من فئات exception، تتجذر في std::exception. تشمل الفئات المشتقة الشائعة std::runtime_error لمشكلات وقت التشغيل وstd::logic_error لأخطاء البرمجة. توفر جميع exception القياسية method باسم what() يعيد message خطأ.
عند طرح استثناء، تتراجع مكدسة الاستدعاءات - ويتم استدعاء المُدمِّرات لجميع الكائنات المحلية بينما يبحث البرنامج عن كتلة catch مطابقة. يضمن ذلك التنظيف السليم حتى عند حدوث أخطاء، ولهذا السبب يعمل كلٌّ من RAII ومعالجة الاستثناءات معًا بشكل جيد.
التحدي
سهللنَبْنِ حاسبة قسمة آمنة توضّح معالجة الاستثناءات في سياق كائني التوجه. ستنشئ class باسم Calculator ينفّذ عمليات القسمة ويرمي استثناءات ذات معنى عند حدوث خطأ ما، ثم تعالج تلك الاستثناءات بسلاسة في برنامجك الرئيسي.
ستنظّم الكود عبر ثلاثة ملفات:
Calculator.h: Define classCalculatorالذي ينفّذ العمليات الرياضية مع معالجة مناسبة للأخطاء.يجب أن يحتوي
Calculatorعلى method باسمdivide(double numerator, double denominator)يعيد نتيجة القسمة. ومع ذلك، إذا كان denominator يساوي صفراً، فيجب أن يرميstd::runtime_errorمع messageDivision by zero. وإذا كان numerator أو denominator سالباً، فيجب أن يرميstd::invalid_argumentمع messageNegative values not allowed.أضف أيضاً method باسم
safeSqrt(double value)يعيد الجذر التربيعي للقيمة. إذا كانت القيمة سالبة، فارْمِstd::runtime_errorمع messageCannot compute square root of negative number. ستحتاج إلى تضمين<cmath>من أجل functionsqrt.Calculator.cpp: Implement الـ methods المعلنة في ملف الرأس. احرص على تضمين ملفات الرأس اللازمة (<stdexcept>و<cmath>) والتحقق من الشروط قبل تنفيذ العمليات.main.cpp: اقرأ ثلاثة inputs (كل واحد في سطر منفصل):- نوع العملية: إما
divideأوsqrt - العدد الأول (double)
- العدد الثاني (double): يُستخدم فقط للقسمة، ويتم تجاهله مع sqrt
أنشئ كائناً من
Calculatorونفّذ العملية المطلوبة داخل كتلة try. إذا نجحت العملية، اطبعResult: [value]. وإذا تم التقاط exception، فاطبعError: [message]، حيث تأتي message من methodwhat()الخاصة بالاستثناء.بعد كتلة try-catch (بغض النظر عما إذا حدث exception أم لا)، اطبع
Calculation completeلتوضيح أن البرنامج يواصل التنفيذ بعد معالجة الاستثناءات.- نوع العملية: إما
على سبيل المثال، عند استخدام inputs divide و10 و2:
Result: 5
Calculation completeعند استخدام inputs divide و10 و0:
Error: Division by zero
Calculation completeعند استخدام inputs divide و-5 و2:
Error: Negative values not allowed
Calculation completeعند استخدام inputs sqrt و16 و0:
Result: 4
Calculation completeعند استخدام inputs sqrt و-9 و0:
Error: Cannot compute square root of negative number
Calculation completeلاحظ كيف يتعامل البرنامج بسلاسة مع الأخطاء من خلال التقاط exceptions وعرض messages ذات معنى، ثم يواصل التنفيذ. يحافظ هذا النمط على نظافة معالجة الأخطاء لديك، ويُبقي منطقك الرئيسي مركزاً على المسار السليم.
جرّب بنفسك
#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())
// - After the try-catch, always print "Calculation complete"
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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مراجعة - الحاويات العامةتدرّب بنفسك: مترجم C++ عبر الإنترنت