معالجة الأخطاء في C++: try/catch والتقنيات المتقدمة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

معالجة الأخطاء في C++: من try/catch إلى الأساليب الحديثة

\n\n

آلية الاستثناءات في C++ هي أداة قوية ولكنها تتطلب فهماً عميقاً. بينما الاستخدام الأساسي لـ try/catch معروف لدى معظم المطورين، فإن المعالجة الاحترافية للأخطاء تشمل أكثر من ذلك بكثير: من ضمانات أمان الاستثناءات إلى الاستراتيجيات البديلة بدون استثناءات. في هذه المقالة، سنقوم بتحليل التقنيات المتقدمة للتعامل مع الأخطاء في لغة C++ الحديثة.

\n\n

1. ضمانات أمان الاستثناءات: أساس الكود الموثوق

\n\n

أي دالة يمكنها إلقاء استثناء يجب أن توفر واحداً من ثلاثة ضمانات أمان:

\n\n
    \n
  • الضمان الأساسي (Basic Guarantee): في حالة الاستثناء، يتم الحفاظ على ثوابت الكائن، ولا يوجد تسرب للموارد. قد يكون الكائن في حالة غير متوقعة ولكنها صالحة.
  • \n
  • الضمان القوي (Strong Guarantee): إما أن يتم تنفيذ العملية بالكامل، أو تبقى حالة البرنامج دون تغيير (الذرية). يتم تحقيقه من خلال نمط copy-and-swap.
  • \n
  • ضمان عدم وجود استثناءات (Noexcept Guarantee): الدالة لا تلقي استثناءات أبداً. أمر بالغ الأهمية للمدمرات، وعمليات النقل، والعمليات المستخدمة في الحاويات.
  • \n
\n\n
// مثال على الضمان القوي من خلال copy-and-swap\nclass Vector {\n    int* data;\n    size_t sz, cap;\npublic:\n    void push_back(int value) {\n        if (sz == cap) {\n            // 1. إنشاء نسخة\n            Vector tmp(*this);\n            // 2. تعديل النسخة (قد تلقي استثناء)\n            tmp.reserve(cap * 2);\n            // 3. التبادل (noexcept!)\n            swap(tmp);\n        }\n        data[sz++] = value;  // noexcept\n    }\n    \n    void swap(Vector& other) noexcept {\n        std::swap(data, other.data);\n        std::swap(sz, other.sz);\n        std::swap(cap, other.cap);\n    }\n};
\n\n

2. RAII كأساس لإدارة الموارد

\n\n

نمط RAII (اكتساب الموارد هو التهيئة) ليس مجرد مؤشرات ذكية. إنه مبدأ أساسي يسمح بتجنب تسرب الموارد عند حدوث الاستثناءات:

\n\n
    \n
  • يتم الاستحواذ على المورد في المُنشئ
  • \n
  • يتم تحريره في المُدمّر (الذي يكون افتراضياً noexcept)
  • \n
  • الاستثناء في المُدمّر هو سلوك غير محدد (UB)
  • \n
\n\n
// غلاف RAII لوصف ملف\nclass FileDescriptor {\n    int fd;\npublic:\n    explicit FileDescriptor(const char* path) \n        : fd(open(path, O_RDONLY)) {\n        if (fd == -1) throw std::runtime_error("فشل فتح الملف");\n    }\n    \n    ~FileDescriptor() noexcept {\n        if (fd != -1) close(fd);\n    }\n    \n    // منع النسخ، السماح بالنقل\n    FileDescriptor(FileDescriptor&& other) noexcept : fd(other.fd) {\n        other.fd = -1;\n    }\n};
\n\n

3. البدائل الحديثة للاستثناءات: std::optional و std::expected

\n\n

ليست كل الأخطاء يجب معالجتها من خلال الاستثناءات. للأخطاء المتوقعة (إدخال غير صحيح، عدم وجود بيانات) من الأفضل استخدام أنواع التغليف:

\n\n
#include <optional>\n#include <expected>  // C++23\n\n// std::optional — للقيم الاختيارية\nstd::optional<int> safe_divide(int a, int b) {\n    if (b == 0) return std::nullopt;\n    return a / b;\n}\n\n// std::expected — للأخطاء مع المعلومات (C++23)\nstd::expected<double, std::string> sqrt_checked(double x) {\n    if (x < 0) return std::unexpected("مدخل سالب");\n    return std::sqrt(x);\n}\n\n// الاستخدام\nauto result = safe_divide(10, 0);\nif (result) {\n    std::cout << *result;\n} else {\n    std::cout << "القسمة على صفر";\n}
\n\n

4. noexcept: المواصفات والتحسينات

\n\n

المُحدد noexcept ليس مجرد توثيق. إنه يؤثر على الأداء و

المدونات

توصيات الكتب