C++: معالجة الاستثناءات
آخر تحديث: 2026-08-26
عند تشغيل البرامج، تحدث دائماً أشياء غير متوقعة: ملفات غير موجودة، نفاد الذاكرة، انقطاع الشبكة...
إذا تُركت دون معالجة، ينهار البرنامج.
معالجة الاستثناءات هي "وسادة الهواء" للبرنامج - عند حدوث مشاكل، يستجيب بلباقة بدلاً من الانهيار.
1. أساسيات معالجة الاستثناءات
(1) 1.1 ما هو الاستثناء؟
الاستثناء هو خطأ أو حالة غير متوقعة تحدث أثناء تنفيذ البرنامج.
استثناءات شائعة:
- القسمة على صفر
- الوصول إلى مؤشر فارغ
- فشل فتح ملف
- فشل تخصيص الذاكرة
(2) 1.2 العناصر الثلاثة لمعالجة الاستثناءات
تستخدم معالجة استثناءات C++ ثلاث كلمات مفتاحية:
| الكلمة المفتاحية | الوظيفة |
|---|---|
try |
مراقبة الكود الذي قد يُلقي أخطاء |
catch |
التقاط ومعالجة الاستثناءات |
throw |
إلقاء استثناء |
(3) 1.3 مثال أساسي
مثال: استثناء القسمة على صفر (الصعوبة ⭐)
▶ مثال 1: مثال برمجي (الصعوبة ⭐)
#include <iostream>
int divide(int a, int b) {
if (b == 0) {
throw "Divisor cannot be 0!"; // إلقاء استثناء
}
return a / b;
}
int main() {
try {
std::cout << divide(10, 2) << std::endl; // طبيعي
std::cout << divide(10, 0) << std::endl; // يُلقي استثناء
}
catch (const char* msg) { // التقاط استثناء
std::cerr << "Error: " << msg << std::endl;
}
std::cout << "Program continues running" << std::endl;
return 0;
}
الخرج:
5
Error: Divisor cannot be 0!
Program continues running
نتيجة التنفيذ:
5
Error: Divisor cannot be 0!
Program continues running
💡 نصيحة:
- بعد التقاط الاستثناء، البرنامج لا ينهار - يستمر في التنفيذ
2. أنواع الاستثناءات
(1) 2.1 الأنواع التي يمكن إلقاؤها
يمكن لـ throw إلقاء أي نوع:
| النوع | مثال |
|---|---|
| الأنواع الأساسية | throw 42; |
| النصوص | throw "error"; |
| الاستثناءات القياسية | throw std::runtime_error("error"); |
| استثناءات مخصصة | throw MyException(); |
(2) 2.2 التقاط استثناءات متعددة
مثال: التقاط أنواع مختلفة من الاستثناءات (الصعوبة ⭐⭐)
#include <iostream>
### ▶ مثال 2: مثال برمجي (الصعوبة ⭐)
#include <string>
void process(int value) {
if (value == 0) {
throw 0; // إلقاء int
}
if (value < 0) {
throw std::string("Negative number"); // إلقاء string
}
}
int main() {
try {
process(-5);
}
catch (int e) {
std::cerr << "Caught int exception: " << e << std::endl;
}
catch (const std::string& e) {
std::cerr << "Caught string exception: " << e << std::endl;
}
return 0;
}
3. الاستثناءات القياسية
(1) 3.1 هرمية std::exception
تُعرّف مكتبة C++ القياسية مجموعة من فئات الاستثناءات، جميعها في stdexcept:
std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::out_of_range
│ └── std::length_error
└── std::runtime_error
├── std::overflow_error
└── std::underflow_error
(2) 3.2 الاستثناءات القياسية الشائعة
| فئة الاستثناء | الوصف |
|---|---|
std::invalid_argument |
معامل غير صالح |
std::out_of_range |
خارج الحدود |
std::runtime_error |
خطأ في وقت التنفيذ |
std::bad_alloc |
فشل تخصيص الذاكرة (فشل new) |
مثال: استخدام الاستثناءات القياسية (الصعوبة ⭐⭐)
#include <iostream>
#include <stdexcept>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3};
try {
v.at(10); // وصول خارج الحدود
}
catch (const std::out_of_range& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
4. استثناءات مخصصة
(1) 4.1 الوراثة من std::exception
أفضل ممارسة: يجب أن ترث فئات الاستثناءات المخصصة من std::exception.
مثال: فئة استثناء مخصصة (الصعوبة ⭐⭐⭐)
#include <iostream>
#include <exception>
#include <string>
// فئة استثناء مخصصة
class MyException : public std::exception {
private:
std::string msg;
public:
MyException(const std::string& msg) : msg(msg) {}
const char* what() const noexcept override {
return msg.c_str();
}
};
int main() {
try {
throw MyException("Custom exception");
}
catch (const MyException& e) {
std::cerr << "Caught custom exception: " << e.what() << std::endl;
}
return 0;
}
5. أمان الاستثناءات
(1) 5.1 ما هو أمان الاستثناءات؟
أمان الاستثناءات يعني أنه حتى عند إلقاء استثناء، يحافظ البرنامج على حالة متسقة (لا تسرب موارد، لا تلف بيانات).
ثلاثة مستويات ضمان:
- الضمان الأساسي: حالة البرنامج متسقة، لكن قد تكون قد تغيرت
- الضمان القوي: العملية إما تنجح أو تتراجع بالكامل (كأنها لم تحدث)
- ضمان عدم الإلقاء: الدالة لا تُلقي استثناء أبداً
(2) 5.2 RAII - أداة فعالة لإدارة الموارد
RAII (Resource Acquisition Is Initialization): إدارة الموارد عبر الكائنات، مع تحريرها تلقائياً في المدمرات.
مثال: استخدام المؤشرات الذكية لأمان الاستثناءات (الصعوبة ⭐⭐⭐)
#include <iostream>
#include <memory>
void process() {
// استخدام unique_ptr لإدارة الذاكرة؛ حتى لو أُلقي استثناء، ستُحرر تلقائياً
std::unique_ptr<int> p(new int(42));
// ... كود قد يُلقي استثناءات ...
std::cout << *p << std::endl;
} // p يُحرر تلقائياً
int main() {
try {
process();
}
catch (...) {
std::cerr << "Caught exception" << std::endl;
}
return 0;
}
6. الكلمة المفتاحية noexcept
(1) 6.1 الاستخدام الأساسي
أدخلت C++11 noexcept، التي تعلن أن دالة لا تُلقي استثناءات.
مثال: الإعلان عن عدم الإلقاء (الصعوبة ⭐)
void safeFunction() noexcept {
// هذه الدالة تضمن عدم إلقاء استثناءات
}
void unsafeFunction() {
// قد تُلقي استثناءات
}
💡 نصيحة:
- المدمرات
noexceptافتراضياً noexceptتساعد المترجم على التحسين
❓ أسئلة شائعة
س: متى أستخدم الاستثناءات؟ ج: للحالات غير المتوقعة حقاً، وليس لتدفق التحكم العادي.
س: ما هو catch(...)؟ ج: يلتقط جميع الاستثناءات، يُستخدم عادةً لتنظيف الموارد قبل إعادة الإلقاء.
try {
// ...
}
catch (...) {
// تنظيف الموارد
throw; // إعادة الإلقاء
}
س: هل يمكن للبانيات إلقاء استثناءات؟ ج: نعم، لكن احذر من تسرب الذاكرة. استخدم المؤشرات الذكية أو RAII لإدارة الموارد.
▶ مثال 3: التقاط الاستثناءات (الصعوبة ⭐)
#include <iostream>
#include <stdexcept>
double divide(double a, double b) {
if (b == 0) {
throw std::runtime_error("Divisor cannot be 0");
}
return a / b;
}
int main() {
try {
std::cout << divide(10, 2) << std::endl;
std::cout << divide(10, 0) << std::endl;
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}
الخرج:
Error: Divisor cannot be 0
try-catch يلتقط الاستثناءات، throw يُلقي الاستثناءات. what() تُرجع رسالة الخطأ.
| المفهوم | النقطة الأساسية |
|---|---|
| try-catch-throw | العناصر الثلاثة لمعالجة الاستثناءات |
| الاستثناءات القياسية | std::runtime_error وغيرها |
| استثناءات مخصصة | ترث من std::exception |
| أمان الاستثناءات | RAII يضمن عدم تسرب الموارد |
| noexcept | تعلن أن الدالة لا تُلقي استثناءات |
📖 ملخص
- try/catch: التقاط ومعالجة الاستثناءات
- throw: إلقاء الاستثناءات
- std::exception: فئة الاستثناء القياسية الأساسية
- استثناءات مخصصة: طبّق فئات استثناءاتك الخاصة بالوراثة من
std::exception
📝 تمارين
-
أساسي (الصعوبة ⭐): اكتب دالة قسمة تُلقي
std::runtime_errorعندما يكون القاسم 0، واستخدمtry/catchفيmainلالتقاط وإخراج رسالة الخطأ. -
متوسط (الصعوبة ⭐⭐): عرّف فئة استثناء مخصصة (ترث من
std::exception) بحقلين: رمز الخطأ ووصف الخطأ. بعد إلقائها، استرجع كليهما في كتلة catch. -
متقدم (الصعوبة ⭐⭐⭐): طبّق فئة "حارس موارد" تكتسب الموارد في الباني وتُحررها في المدمر. حتى لو أُلقي استثناء بينهما، تُحرر الموارد بشكل صحيح (مبدأ RAII).
- الأجزاء الثلاثة لمعالجة الاستثناءات: try يراقب، throw يُلقي، catch يلتقط
- فئات الاستثناءات القياسية ترث من std::exception
- catch يطابق بالنوع؛ كتل catch المتعددة تُفحص من الأعلى إلى الأسفل
- فك تكديس الاستدعاءات: بعد إلقاء استثناء، يُبحث عن catch طبقة بطبقة
- noexcept تعلن أن الدالة لا تُلقي استثناءات
الدرس التالي: عمليات الملفات المتقدمة (#41)