Using Explicit In Constructor مقاله بسيطه حول استخدام explicit

الناقل : elmasry | الكاتب الأصلى : Wajdy Essam | المصدر : www.arabteam2000-forum.com

السلام عليكم ورحمه الله وبركاته ،،

مترجم سي++ مترجم خدوم للغايه :) حيث يقوم في بعض الأحيان "بخدمه" وهي التحويل من نوع بيانات عادي الى كائن وبالتالي يستدعي داله البناء وهو ما يعرف بـ implicit conversion ..

في البدايه قد يبدوا لك الأمر مريح جدا وبلا مشاكل ،، شاهد المثال التالي :

انسخ الكود
 
 
[color= #007f00;]#include
using namespace std ;
 
class Student
{
private :
        int mNumber  ;
        string mName ;
 
public :
        Student(int num,string s=""):mName(s),mNumber(num) {}
        Student()       { mNumber=0 ; mName="" ; }
 
 
        string getName () const { return mName ; }
        int getNumber  () const { return mNumber; }
};
 
void show ( const Student& std )
{
cout << "Name   : " << std.getName() << endl
     << "Number : " << std.getNumber() << endl << endl;
}
 
int main (int argc ,char** argv)
{
        Student s1(2,"Ahmed");
        show(s1);
 
        Student s2 = 5 ;
        show(s2);
 
        Student s3 ;
        s3 = 4 ;                
        show(s3);
 
        show(56);       // implicit conversion
}
 
 
 
 


في المثال أعلاه ، نشاهد الكائن الاول s1 أنشأناه بالطريقه العاديه .. كائن عادي موجود في Stack وقد تم استدعاء داله البناء للقيام بالتهيئه ، جميل ...

الكائن s2 ، وهو حاله أخرى يسمح بها المترجم ، حيث سيتم استدعاء داله البناء لهذا الكائن ..وهو مقبوله لكنها غريبه قليلا ...

الكائن الثالث s3 ، تم استدعاء داله البناء التي تأخذ ولا وسيط ، ثم بعدها السطر :
s3 = 4;


هنا في هذا السطر ، سوف يقوم المترجم بعمليتين ، الأولى تحويل المتغير 4 الى كائن من نوع Student (سوف يتم استدعاء داله البناء هنا) ، ثم يقوم بعمليه الأسناد الى الكائن s3 (وسوف يتم استدعاء داله operator= التي يقدمها لك المترجم أو التي قمت بكتابتها) ..

هذا هو الـ implicit conversion الذي تكلمت عنه ، يقوم المترجم بهذه الخطوه ويحول الرقم الى كائن نيابه عنك ، وهي خدمه جليله يجب أن تشكره عليها :) .

لكن دعنا ننظر الى السطر التالي ، فيبدوا غريب حقا :
show(56);


ما هذا ؟؟
مثل هذه السطر قد يشوه Confusing على العميل الذي يستخدم مكتباتك ، فأنت ذكرت في الـ Documentation أن هذه الداله تستقبل مرجع للكائن الذي سوف ترسله لها ، وها هي الأن تستقبل رقم من integer .. همممم المترجم المجنون ، تبا لك أيها المترجم :PP ، أنت طيب النيه حقا ، لكن مثل هذه العمليات قد توقعك في مشاكل أنت في غني عنها ..

حسنا ، لا نريد هذه الخدمه من المترجم ، لا نريد المترجم أن يتدخل أبدا ولا يحول لي أي متغير الى كائن ، كل شيء ستقوم به أنت يدويا ، وبدل من هذا السطر :
Student s2 = 5;

يصبح :
Student s2 = Student(5);


وبدل من :
s3 = 4;

يصبح :
s3 = (Student)4;


وبدل من :
show(56);

يصبح :
show(Student(56));


الأن لكي نقوم بمنع المترجم من عمليه التحويل هذه ، نقوم باضافه الكلمه المحجوزه explicit في داله البناء ، وتصبح بهذا الشكل :
explicit Student(int num,string s=""):mName(s),mNumber(num) {}


المشكله في المثال السابق أنك استدعيت الداله بالرقم ، ربما لن تكون مشكله كبيره سواء ارسلت كائن أو أرسلت رقم والمترجم حوله لك ، في النهايه نفس الناتج ، اليس كذلك ؟؟

كلام صحيح ، ولكن هناك لحظات "يجب" أن تمنع المترجم من عمليه التحويل ، شاهد المثال التالي لكي يوضح الفكره ، وهو تقريبا نغس المثال الأول ببعض التغييرات :

انسخ الكود
 
 
#include
using namespace std ;
 
class Student
{
private :
        int mNumber  ;
        string mName ;
 
public :
        Student(int num):mNumber(num) {}
        Student (string s) : mName(s) { }
        Student()       { mNumber=0 ; mName="" ; }      
 
        Student operator + (const Student& rhs);
 
        string getName () const { return mName ; }
        int getNumber  () const { return mNumber; }
};
 
Student Student :: operator+ (const Student& rhs)
{
        Student tmp = mNumber + rhs.mNumber ;
        return tmp ;
}
 
void show ( const Student& std )
{
cout << "Name   : " << std.getName() << endl
   << "Number : " << std.getNumber() << endl << endl;
}
 
int main (int argc ,char** argv)
{
        Student s1(3);
        Student s2(4);
 
        Student s3 ;
        s3 = s1 + s2 ;
                 s3 = s3 + 3 ;
        show(s3);
 
        string st = "dd" ;
        Student s4 ;
        s4 = s1 + st ;
        show(s4);      
}
 
 
 


هنا في الداله الرئيسيه ، أنشأنا كائننين s1 و s2 ،، ثم :
Student s3;
s3 = s1 + s2;
s3 = s3 + 3;
show(s3);


لاحظ انه تم استدعاء المعامل + وتمرير s2 كمعامل لهذه الداله .. والناتج هنا هو كائن بالقيمه 7 .. الأن السطر التالي هنا سوف تكون هناك عمليه Conversion ،، وسيتم تحويل 3 الى كائن ثم يتم تمريرها للداله + . والناتج هو 10 ..

نكمل في المثال ، ونرى الان المشكله الكبيره في عمليه التحويل هذه :
string st = "dd";
Student s4;
s4 = s1 + st;
show(s4);


لاحظ قام المبرمج بجمع متغير من نوع string مع كائن ، وطبعا العمليه صحيحه ، لأن المتغير سيتم تحويله أولا الى كائن (لأن هناك داله بناء تستقبل string) ، ثم يتم ارسال هذا المتغير الى الداله ... وهنا تكون المشكله !! لذلك الحل بوضع explicit في داله البناء التي تستقبل string .. وبعدها بالطبع عندما تترجم هذا السطر سوف يشتكي منك المترجم ..

هذا كل في موضوع الexplicit و الimplicit conversion :happy: ،، أخر نقطه وهي بعيده قليلا لكن لا بأس بتوضيحها وهي عندما نعيد تعريف المعاملات (+،-،*،/) ، مثل هذه المعاملات سوف تكون الداله الخاصه تستقبل كائن من نوع الكلاس ، مثلا :
Student operator+ (const Student& rhs);


الان عندما نستدعي هذه الداله سوف تكون بالشكل التالي :
s3 = s2 + 3; // if no explicit

هنا الطريقه صحيحه ، لكن ماذا لو كان العكس :
s3 = 3 + s3;


طبعا الكود خاطئ ، والسبب أن المترجم سوف ينظر أولا الى المتغير سوف ينظر الى الاشاره ثم سيجد كائن وبالتالي لن يستطيع الجمع ، لكن في المثال السابق سوف يستطيع وذلك لأن بعد الكائن سوف تأتي علامه الجمع مباشره .. السبب أن تعريف المعامل + يشترط أنه يجب أن يستدعي تلك العلامه كائن من نفس الكلاس ..

في حال أردنا ان نقوم بالعمليه :
s3 = 3 + s3;


يمكن ان نعرف الداله operator+ خارج الكلاس (وجعلها صديقه في حال أردنا الوصول للprivate) ، بالشكل التالي :
student& operator+ (const Student& s1 , const Student& s2);


وبالتالي عندما نستدعي :
s3 = 3 + s3;


سوف يقوم المترجم بعمليه التحويل من 3 الى كائن من نوع Student ومن ثم يرسل الكائنين الى الداله operator+ .

حسنا ، مارأيك في :
s3 = 3 + 3;


ماذا سيحصل هنا :PP

أرجوا أن تكون المقاله واضحه :) ، فقد كتبتها الأن ولم أراجعها ، لذلك في حال وجود خطأ عليكم التوضيح ..