معالجة الأخطاء في C++: من try/catch إلى RAII و noexcept
تعتمد موثوقية البرنامج بشكل مباشر على كيفية استجابته للمواقف غير المتوقعة. في لغة C++، الآلية الأساسية لذلك هي الاستثناءات. في هذه المقالة، سنقوم بتحليل البنية التقليدية try/catch/throw، بالإضافة إلى مناقشة الأساليب البديلة والحديثة: RAII، noexcept، ومعالجة الأخطاء بدون استثناءات.
1. الآلية الأساسية: try, catch, throw
الاستثناء هو كائن يتم "رميه" (throw) عند حدوث خطأ. يمكن للبرنامج التقاطه (catch) وتنفيذ إجراءات تصحيحية دون مقاطعة العمل بالكامل.
الصياغة (Syntax)
#include <iostream>#include <stdexcept>
double divide(double a, double b) { if (b == 0.0) { throw std::runtime_error("القسمة على صفر!"); } return a / b;}
int main() { try { double result = divide(10.0, 0.0); std::cout << "النتيجة: " << result << std::endl; } catch (const std::runtime_error& e) { std::cerr << "خطأ: " << e.what() << std::endl; } return 0;}قواعد العمل
- throw — ينشئ نسخة من الكائن وينقل التحكم إلى المعالج.
- try — كتلة من الكود حيث قد تحدث استثناءات.
- catch — يلتقط الاستثناء حسب النوع. يمكن تحديد عدة كتل
catch. - إذا لم يتم التقاط الاستثناء، يتم استدعاء
std::terminate().
الالتقاط باستخدام الفئة الأساسية
جميع الاستثناءات القياسية ترث من std::exception. لذلك يمكن كتابة معالج عام:
try { // كود} catch (const std::exception& e) { std::cerr << "خطأ قياسي: " << e.what() << std::endl;} catch (...) { std::cerr << "استثناء غير معروف" << std::endl;}كتلة catch (...) تلتقط جميع الاستثناءات بشكل عام، ولكن بدون الوصول إلى الكائن.
2. الاستثناءات في المنشئات (Constructors) والمدمرات (Destructors)
المنشئات (Constructors)
إذا قام المنشئ برمي استثناء، يعتبر الكائن غير منشأ، ولن يتم استدعاء المدمر له على الإطلاق. هذا مهم لإدارة الموارد. مثال:
class Resource {public: Resource() { std::cout << "تم تخصيص المورد" << std::endl; throw std::runtime_error("خطأ في التهيئة"); } ~Resource() { std::cout << "تم تحرير المورد" << std::endl; }};
int main() { try { Resource r; } catch (...) { std::cout << "تم التقاط الاستثناء" << std::endl; } // لن يتم استدعاء مدمر Resource! return 0;}المدمرات (Destructors)
رمي الاستثناءات من المدمر — ممارسة سيئة. إذا تم استدعاء المدمر أثناء فك المكدس (stack unwinding)، وقام برمي استثناء جديد، سينتهي البرنامج بشكل غير طبيعي. اكتب دائمًا المدمرات على أنها noexcept (وهي كذلك افتراضيًا).
3. RAII — إدارة الموارد التلقائية
الاصطلاح RAII (Resource Acquisition Is Initialization) هو مفتاح الكود الآمن. يتم الاستحواذ على المورد (ذاكرة، ملف، mutex) في المنشئ وتحريره في المدمر. إذا حدث استثناء، يتم استدعاء مدمرات جميع الكائنات المنشأة تلقائيًا (فك المكدس - stack unwinding)، ويتم تحرير الموارد.
مثال مع المؤشر الذكي std::unique_ptr:
#include <memory>#include <iostream>
void process() { auto ptr = std::make_unique<int>(42); // إذا حدث استثناء هنا، // فسيقوم unique_ptr تلقائيًا بحذف الذاكرة throw std::runtime_error("خطأ في المعالجة");}