تعدد الاستثناءات في ++C
تعدد الاستثناءات
يمكننا أن ندخل ألية الاستثناء لمتابعة أي عدد من الأخطاء المحتملة , ولبيان ذلك نعدل من البرنامج السابق لنفصل بين متابعة خطأ إدخال عدد زائد , وخطأ إدخال عدد زائد وخطأ سحب عدد زائد بحيث يكون لكل فئته الاستثنائية الخاصة به :
Xstack2.cpp
// xstak2.cpp
// demonstrates two exception handlers
// UCS Laboratories
#include <iostream.h>
const int MAX = 3; // stack holds 3 ints
class Stack
{
private:
int st[MAX]; // stack: array of integers
int top; // index of top of stack
public:
class Full { }; // exception class
class Empty { }; // exception class
Stack() // constructor
{ top = -1; }
void push(int var) // put number on stack
{
if(top >= MAX-1) // if stack full,
throw Full(); // throw Full exception
st[++top] = var;
}
int pop() // take number off stack
{
if(top < 0) // if stack empty,
throw Empty(); // throw Empty exception
return st[top--];
}
};
void main()
//UCS Laboratories
{
Stack s1;
try
{
s1.push(11);
s1.push(22);
s1.push(33);
// s1.push(44); // oops: stack full
cout << "1: " << s1.pop() << endl;
cout << "2: " << s1.pop() << endl;
cout << "3: " << s1.pop() << endl;
cout << "4: " << s1.pop() << endl; // oops: stack empty
}
catch(Stack::Full)
{
cout << "Stack Full" << endl;
}
catch(Stack::Empty)
{
cout << "Stack Empty" << endl;
}
}
ويمكننا من مقارنة البرنامجين أن تري كيف استخدمت ألية الاستثناءات أكثر من مرة .
تطبيق الاستثناءات مع الفئة distance
سوف نطبق ألية الاستثناء علي الفئة المذكورة لكي نضمن ألا يكون بيان البوصات أكبر من 12 :
Xdist.cpp
// xdist.cpp
// exceptions with Distance class
// UCS Laboratories
#include <iostream.h>
#include <string.h> // for strcpy()
class Distance // English Distance class
{
private:
int feet;
float inches;
public:
class InchesEx { }; // exception class
Distance() // constructor (no args)
{ feet = 0; inches = 0.0; }
Distance(int ft, float in) // constructor (two args)
{
if(in >= 12.0) // if inches too big,
throw InchesEx(); // throw exception
feet = ft;
inches = in;
}
void getdist() // get length from user
{
cout << "\nEnter feet: "; cin >> feet;
cout << "Enter inches: "; cin >> inches;
if(inches >= 12.0) // if inches too big,
throw InchesEx(); // throw exception
}
void showdist() // display distance
{ cout << feet << "\'-" << inches << '\"'; }
};
void main()
{
try
{
Distance dist1(17, 3.5); // 2-arg constructor
Distance dist2; // no-arg constructor
dist2.getdist(); // get distance
// display distances
cout << "\ndist1 = "; dist1.showdist();
cout << "\ndist2 = "; dist2.showdist();
}
catch(Distance::InchesEx) // catch exceptions
{
cout << "\nInitialization error: "
"inches value is too large.";
}
}
وتري في المثال أن فئة التعامل مع الاستثناء هي الفئة (الفارغة) InchEx وأن استثارتها موضوع في موضعين ؛ البادئة ذات المعاملين (الخاصة بالاستهلال) , وفي دالة إدخال البيانات .
وقد تري أن يكون تنفيذ الاستثناء بطريقة أكثر فعالية فمثلا , أن يعود التحكم لبداية كتلة التجربة لإعطاء المستخدم فرصة تصحيح خطئه .
الاستثناء ذو المعاملات
يمكننا تطوير ألية الاستثناء بحيث تظهر لنا بيانات عن الخطأ , ففي مثالنا السابق , قد نريد أن نعرف قيمة البيان المتجاوز الحد , وان نعلم مصدر حدوثه ؛ هل البادئة ذات المعاملين أم دالة الإدخال .
ويتم التطوير بأن تستغل فئة الاستثناء في تجهيز كائنها ببيانات ودوال يمكن أن تستخدم حين استثارة الخطأ , كما تري في المثال التالي :
Xdist2.cpp
// xdist2.cpp
// exceptions with arguments
// UCS Laboratories
#include <iostream.h>
#include <string.h> // for strcpy()
class Distance // English Distance class
{
private:
int feet;
float inches;
public:
class InchesEx // exception class
{
public:
char origin[80]; // for name of routine
float iValue; // for faulty inches value
InchesEx(char* or, float in) // 2-arg constructor
{
strcpy(origin, or); // store string
iValue = in; // store inches
}
}; // end of exception class
Distance() // constructor (no args)
{ feet = 0; inches = 0.0; }
Distance(int ft, float in) // constructor (two args)
{
if(in >= 12.0)
throw InchesEx("2-arg constructor", in);
feet = ft;
inches = in;
}
void getdist() // get length from user
{
cout << "\nEnter feet: "; cin >> feet;
cout << "Enter inches: "; cin >> inches;
if(inches >= 12.0)
throw InchesEx("getdist() function", inches);
}
void showdist() // display distance
{ cout << feet << "\'-" << inches << '\"'; }
};
void main()
//UCS Laboratories
{
try
{
Distance dist1(17, 3.5); // 2-arg constructor
Distance dist2; // no-arg constructor
dist2.getdist(); // get value
// display distances
cout << "\ndist1 = "; dist1.showdist();
cout << "\ndist2 = "; dist2.showdist();
}
catch(Distance::InchesEx ix) // exception handler
{
cout << "\nInitialization error in " << ix.origin
<< ".\n Inches value of " << ix.iValue
<< " is too large.";
}
}
وتري أن التطوير المطلوب تم في ثلاثة مواضع :
1- فئة الاستثناء : لم تعد فارغة , فقد جهزت ببيانات هي مصفوفة تحتوي العبارة التي ستحدد مصدر الخطأ , وقيمة البيان الخاطئ , كما جهزت بادئتها بمعاملين يضمنان العبارة والقيمة . ومن المناسب أن يكون بيانات فئة الاستثناء عامة (ليست خاصة كالمعتاد) , لكي يمكن لكتلة التنفيذ أن يتعامل معها .
2- استهلال كائن الاستثناء : عند استثارة الاستثناء , يكون استهلال البادئة ذات المعاملين بالعبارة والقيمة المطلوبة . فالأمر :
Throw InceEx(''2-arg constructor'',in);
يجعل العبارة المشار إليها (المعامل الأول) والقيمة المبينة (المعامل الثاني) , يظهران عند استثارة الخطأ .
3- استخلاص البيان من كائن الاستثناء : تري ذلك من التعديل الوارد علي كتلة التنفيذ , والي استفاد من كون بيانات الكائن عامة , فأمكنه استخلاصها بالمعاملات ix.origin و ix.iValue .
الفئة xalloc
يحتوي نظام بورلاند علي عدد من فئات الاستثناء الجاهزة , يمكن للمبرمج الاستفادة منها وأكثر هذه الاستثناءات شهرة هي xalloc التي تستخدم مع المؤثر new . تستثار هذه الفئة عند عدم إمكانية تحديد حجم الذاكرة المطلوب , وهي معرفة في الملف except.h , ويجب أن تضمنه برنامجك ليمكنك استخدامها . وإليك برنامجا يستفيد من هذه الفئة :
Xalloc.cpp
// xalloc.cpp
// demonstrates xalloc class
// UCS Laboratories
#include <iostream.h>
#include <except.h> // for xalloc class
void main()
{
const unsigned int MAX = 60000; // memory size (chars)
char* ptr; // pointer to memory
try
{
ptr = new char[MAX]; // allocate memry
// other statements that use 'new'
}
catch(xalloc) // exception handler
{
cout << "\nxalloc exception: can't allocation memory.";
exit(1);
}
for(unsigned int j=0; j<MAX; j++) // fill memory with data
*(ptr+j) = j%128;
for(j=0; j<MAX; j++) // check data
if(*(ptr+j) != j%128)
{
cout << "\nData error";
exit(1);
}
delete[] ptr; // release memory
cout << "\nMemory use is successful.";
}
(يمكنك في هذا البرنامج أن تضمن كتلة ما تشاء من أوامر تستخدم المؤثر new) بإمكانك استثارة الخطأ بالتلاعب بقيمة MAX حيث لا يمكنك ملء الذاكرة بأكثر من 60000 بايت فيمكنك تحديد رقم يزيد عن ذلك ومشاهدة استثارة حالة الاستثناء .
وتلاحظ أن الدوارة المستخدمة لملء الذاكرة المخصصة خارج كتلة التجربة , حيث لا تستخدم المؤثر new .
ملاحظات حول استخدام ألية الاستثناء
لقد عرضنا لألية الاستثناء في أبسط وأعم صور استخدامها . وسوف نلخص فيما يلي بعض الأفكار المتعلقة بهذا الاستخدام :
- استدعاء المنهيات أليا : من خصائص ألية الاستثناء أنها بمجرد أن تستثار حالة الاستثناء تستدعي علي التو كافة الدوال المنهية للكائنات التي تكون قد أنشأت في الجزء المحدد التجربة , تمهيدا للتعامل مع الخطأ المتركب .
- إجراءات مواجهة الخطأ : قد تري خروج البرنامج عند حدوث الخطأ , وأن تكون إجراءات الإنهاء بطريقة معينة كأن تخطر المستخدم بنوع الخطأ أو مصدره , أو أن تحرر الذاكرة أو أ]ه عناصر أخري من المعتاد قبل خروج البرنامج , وتساعدك الألية في عملية التخلص من أثار الخطأ بالإستدعاء الألي للمنهيات , مما يمكنك من تحرير أي عنصر من المعتاد يكون الكائن متعاملا معه أثناء التنفيذ . ( يقوم نظام الدوس بتحرير كافة عناصر المعتاد عند خروج برامجه ولكن ذلك ليس متحققا دائما في بيئة الويندوز ) .
ومن جهة أخري قد لا تري خروج البرنامج وأنه يمكن تلافي أثاره وتصحيح الوضع دون التضحية به , كأن تطلب من المستخدم إدخال البيان الصحيح , أو إعادة العملية مع مراعاة الشرط المطلوب . ويتم ذلك عادة بإنشاء دوارة تعيد التنفيذ إلي بداية كتلة التجربة (وقد أعادتها ألية الاستثناء إلي وضعها الابتدائي) .
أما إذا لم يوجد كتلة لتنفيذ الاستثناء يوافق الخطأ المرتكب , فإن نظام التشغيل سينهي البرنامج بطريقة فظة .
- تداخل الدوال المثيرة لحالة الاستثناء : لا يشترط أن يكون أمر استثارة حالة الاستثناء موجود في كتلة التجربة , فيمكن أن يكون في دالة يتم استعاؤها بأمر داخل الكتلة , أو في دالة تستدعي عن طريق دالة تستدعي بأمر داخل الكتلة وهكذا .
- لا عودة لنقطة ما قبل الخطأ : لا يمكن لكتلة التنفيذ أن يعيد التحكم لنقطة سابقة عليه (ما لم تفعل أنت شيئا أخر) .
ملخص
أسلوب ترميز البيانات (التعريف الرمزي لها ) يمكنك من خلق عائلة من الدوال أو الفئات بصياغة واحدة وهي إمكانية يمكنك اللجوء إليها حينما تجد نفسك مضطرا لكتابة عدة أكواد لا يفرق بينها إلا نوع البيانات .
وتتيح ألية الاستثناء أسلوبا متطورا لمعالجة الأخطاء التي ترتكب عند استدعاء الدوال المنتمية للفئات , ويتم ذلك بتضمين الفئة المراد متابعة الأخطاء في دوالها فئة تسمي "فئة الاستثناء" , يستثار كائنها عند حدوث الخطأ بناء علي شرط يوجد في الدالة المنتمية التي يخشي حدوث الخطأ عند استدعائها . توضع الدالة الأخيرة في كتلة يسمي كتلة التجربة , وما أن يستثار الاستثناء حتي ينتقل التحكم في البرنامج لكتلة تنفيذ الاستثناء وهو الكتلة التالي مباشرة لكتلة التجربة .
تعليقات
إرسال تعليق