معالجة الأخطاء المتقدمة في C++: من try/catch إلى الممارسات الصناعية
معالجة الأخطاء هي أحد الركائز الأساسية للبرمجة الصناعية. في C++، لا يتوفر للمطور الآلية الكلاسيكية try/catch فحسب، بل أيضًا ترسانة كاملة من التقنيات المتقدمة: من RAII إلى ضمانات أمان الاستثناءات. في هذه المقالة، سنشرح كيفية كتابة كود C++ موثوق يعالج الأعطال بشكل صحيح، ولا يتسرب منه الموارد، ويظل قابلاً للقراءة.
1. الأساسيات والمزالق القياسية لـ try/catch
الصيغة الأساسية مألوفة للجميع:
#include <iostream>#include <stdexcept>
void dangerousFunction(int value) { if (value < 0) { throw std::invalid_argument("يجب أن تكون القيمة غير سالبة"); } // ... العمل مع value}
int main() { try { dangerousFunction(-5); } catch (const std::invalid_argument& e) { std::cerr << "خطأ: " << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << "خطأ عام: " << e.what() << std::endl; } catch (...) { std::cerr << "تم اكتشاف استثناء غير معروف" << std::endl; } return 0;}نقاط مهمة:
- التقط بالمرجع —
catch (const std::exception& e)يتجنب تقطيع الكائنات. - ترتيب كتل catch مهم: أولاً الاستثناءات الأكثر تحديدًا، ثم العامة.
- لا تفرط في استخدام
catch (...)— فهو يخفي جميع الأخطاء، بما في ذلك الحرجة (مثلstd::bad_alloc). - لا ترمِ الاستثناءات أبدًا من المدمرات — يؤدي هذا إلى سلوك غير محدد (عادةً إلى إنهاء طارئ).
2. RAII كبديل لـ try/catch لإدارة الموارد
RAII (اكتساب الموارد هو التهيئة) — هو النمط الرئيسي في C++ الذي يجعل الاستثناءات آمنة. الفكرة: يتم الاستحواذ على المورد في المُنشئ وتحريره في المُدمّر. إذا حدث استثناء، يتم استدعاء مدمرات جميع الكائنات المنشأة تلقائيًا (تفكيك المكدس).
#include <fstream>#include <vector>#include <stdexcept>
void processFile(const std::string& filename) { std::ifstream file(filename); // RAII: سيتم فتح الملف في المُنشئ if (!file.is_open()) { throw std::runtime_error("لا يمكن فتح الملف: " + filename); }
std::vector<int> data; int value; while (file >> value) { data.push_back(value); } // مُدمّر file سيغلق الملف تلقائيًا، // حتى في حالة حدوث استثناء (مثل bad_alloc أثناء push_back)}مزايا RAII على try/catch اليدوي:
- لا حاجة لكتابة كتل
finally— المدمرات تضمن التنظيف. - يصبح الكود أكثر خطية وقابلية للقراءة.
- الاستثناءات لا تؤدي إلى تسرب الذاكرة، واصفات الملفات، إلخ.
3. ضمانات أمان الاستثناءات
في C++، من المعتاد التمييز بين ثلاثة مستويات من الضمانات للدوال:
- ضمان عدم الرمي — الدالة لا ترمي استثناءات أبدًا. يتم تمييزها بـ
noexcept. - الضمان القوي — إذا حدث استثناء، تظل حالة البرنامج دون تغيير (عملية ذرية).
- الضمان الأساسي — عند حدوث استثناء، لا يحدث تسرب للموارد، وتبقى الكائنات في حالة متسقة، لكن قيمتها قد تتغير.
مثال مع الضمان القوي (طريقة copy-and-swap):
#include <algorithm> // لـ std::swap#include <vector>#include <stdexcept>
class SafeVector { std::vector<int> data_;public: void push_back(int value) { // 1. يتم إنشا