Home برمجة سي/سي++ اجعل المترجم يساعدك في أكتشاف الأخطاء باستخدام const
اجعل المترجم يساعدك في أكتشاف الأخطاء باستخدام const

اجعل المترجم يساعدك في أكتشاف الأخطاء باستخدام const

59
0

في المقال السابق ( لماذا عليك أن تقلل من الأعتماد على Preprocessor ؟)  تحدثنا عن ضروره التقليل من استخدام الـ Preprocessor وخاصه عند تعريف الثوابت ، وذكرنا البديل الأفضل وهو أستخدام const .. مقالنا الليله يدعوك الى استخدام الqualifier المسمى const في أي مكان واي زمان لأنك هكذا ستكون في الـ Safe Side وستتعامل بشكل أكثر صرامه مع عملاء طبقاتك Your Client وسوف يساعدك المترجم في هذه المهمه .

الثوابت يمكن ان تكون في أي جزء في برنامجك ، داخل Block Scope مثلا الداله main ، أو Global Scope ، أو مثلا في داله سواء في القيم المرسله لها Argument أو حتى القيمه الراجعه منها return value .. يمكن أن يكون الثابت داخل الكلاس أو حتى داخل الـ Namespace .. أخيرا يمكن أن يكون مع المؤشرات ، وهنا يوجد له ثلاث استخدامات : مؤشر الى ثابت ، مؤشر ثابت الى متغير ، مؤشر ثابت الى ثابت …

نقوم بعمل Refresh  بسيطه للأساسيات:

لكي لا تقع في متاهه التفكير في الأمثله أعلاه ، دائما أنظر الى علامه * ، اذا كان بعدها على اليمين const فهذا يعني أن المؤشر هو ثابت (لا يؤشر الى شيء أخر) ، أما اذا قبلها (في اليسار) const فهذا يعني أن ما يؤشر اليه pointed to هذا المؤشر ثابت لا تتغير قيمته .. أما اذا ظهرت في الجهتين فمعناه أن الأثنين ثابتين ..

بالنسبه الى مؤشر الى ثابت يمكن أن تكون كلمه const بعد أسم المؤشر (قبل علامه * ) ، مثل :

لذلك دائما أنظر الى علامه * وهي أوضح وأسهل طريقه لكي تعرف الثبوتيه 🙂  ..

استخدام الثوابت يعني أن قيمتها لن تتغير أثناء تنفيذ برنامجك … والمترجم يضمن لك ذلك أثناء ترجمه برنامجك .. حيث أن أي تغيير في أي بت في ذلك الثابت سوف يصرخ المترجم في وجهك ويخرج لك رساله بأنك تحاول أن تغير في ثابت .. ومن هنا كان تعريف الثوابت في سي++ يعرف بـ Bitwise Constness أي أنه لا يمكن تغيير قيمه أي بت في المتغير .

من أشهر أستخدامات الثوابت هي في القيم المرسله والراجعه في الداله .. بالنسبه للقيم المرسله فمعناه أنه الداله لن تغير من قيمه هذا (المتغير أو الكائن ) .. أما بالنسبه الى القيمه الراجعه فمعناه أن القيمه الفلانيه الراجعه من الداله لا يمكن أن تتغير (أي لن تكون في الجهه اليمنى في جمله الأسناد) ، وطبعا استخدام const في القيمه الراجعه أمر مهم ويضمن لك سلامه أستخدام الـ Client للطبقه الخاصه بك ولن توقعهم في مشاكل ما .. لنذكر مثال بسيط يبين الفكره :

السطر الأخير خاطئ ، والسبب أن الـ Left value دائما يجب أن تكون موقع location وليس قيمه …

هذا الكلام بالنسبه الى المتغيرات العاديه Primitive Data Type .. أما بالنسبه للكائنات فلن يكون أي خطأ هناك ، لأنه ناتج جمع كائن مع كائن(بعد اعاده تعرف المعامل +) سوف يكون كائن أخر ،وعمليه الأسناد تتم بشكل عادي (استدعاء لـ = operator) ، مثال بسيط :


العمليه صحيحه ولا يوجد خطأ بالترجمه … ولكنه بالتأكيد خطأ منطقى قام به العميل لطبقتك .. لذلك عليك عند تصميم الطبقه Class أن تراعي مثل هذه الأخطاء بوضع const في القيمه الراجعه وبالتالي لن تكون هذه القيمه أبدا هي LValue ..

ربما قد تقول يستحيل أن يقوم مبرمج بكتابه سطر مثل هذا :

حسنا ما رأيك بهذا :

لقد وقع المبرمج القدير بالفخ ، وخطأ مثل هذا قد يصعب تصحيحه .. لذلك لم لا تدع هذه المهمه للمترجم .. فقط أعلن عن القيمه الراجعه بconst وريح نفسك …

أحد الأشهر الأستخدامات لـ const أيضا هو في داخل الداول في الكلاس const member function ، هذا يعني أن تلك الداله لن تغير في قيمه أي متغير معرف داخل الكلاس (ماعدا static variable) … أيضا هذه الدوال const لا يمكن أن يتم استدعائها الا من خلال الكائنات الثابته const object ..

 

سؤال / ماالفائده من const Member Function بشكل عملي ؟
جواب / لأنها تجعل الـ Interface أكثر وضوحا .. أضافه الى أنها يتم استدعائها بواسطه الكائنات الثابته const object

سؤال / ما الحاجه للكائنات الثابته const object ؟
جواب / استخدام const مع الكائنات لا توجد منه فائده كبيره … لكن Reference to const هو أكثر أستخداما حيث كما نعلم أن التمرير بالمرجع أفضل من التمرير بالقيمه عند التعامل مع الكائنات حيث لن يتم استدعاء الـCopy Constructor ولا Destructor عند الأنتهاء من الداله ..

لذلك دائما تمرير الكائنات يفضل بالمرجع :

  • اذا أدرت تغيير قيمته أرسله بالمرجع فقط By reference
  • اذا لم ترد تغيير قيمته أرسله By reference to const .

وبما أنك تريد ارسال reference to const فلن تستطيع استدعاء أي داله الا أن تكون const ..

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

شاهد المثال :

الأن ترجم البرنامج :وستجد الخطأ بأنك لا يمكن أن تستدعي display من خلال الثابت a .. الحل كما ذكرت باعاده تعريف الداله display ، ليكون لدينا :

في حال قمنا في الداله main بعمل كائن من A واستدعينا display.. في حال كان الكائن ثابت سوف يستدعي display الثابته ، واذا كان عادي سوف يستدعي الداله العاديه .. جرب المثال التالي وشاهد المخرجات :

بعض الأحيان يكون الكائن ثابت ، والداله ثابته .. ولكن يمكنك تغيير القيمه ولن يشتكي المترجم .. والسبب لأنه لا يفكر ولا يعرف ماذا تفعل ، فقط هو يراعي الـ syntax ولا يفكر .. ففي حال أرجعت قيمه من داخل الكلاس بالمرجع ، يمكنك تغيير تلك القيمه التي داخل الكلاس عن طريق مؤشر .. وهو مايعرف بـ logical constness .. أنظر المثال التالي :


لاحظ أننا أرجعنا Reference من المصفوفه ، وقمنا بتغيير هذه القيمه بواسطه مؤشر …

في بعض الحالات الشاذه ، قد تود أن تغير من قيمه متغير داخل الداله الثابته في الكلاس ، فقم باعلان المتغير بـ mutable وبالتالي لن يشتكي المترجم ان قمت بتغيير قيمه المتغير داخل تلك الداله ..

Avoiding Duplication in const and Non-const Member Functions

 

الى هنا ، عرفنا أنه يجب ان نقوم بعمل overload للداله لكي تتعامل مع الconst وnon-const .. لكن المشكله قد تكون في حال كانت الداله كبيره ، سوف يكون هناك تكرار كثير في الكود :

لاحظ :


ما الحل في مشكله تكرار الكود Duplication هنا ؟
أسهل حل بوضع تلك الأجزاء التي تتكرر في داله (private) ونقوم باستدعاء هذه الداله داخل الدالتين ..

حل أخر وهو جعل الداله العاديه تستدعي الداله الثابته ، وذلك بتحويل الكائن الحالى الى ثابت ، واستدعاء الداله الثابته وعندما ترجع القيمه الثابته نقوم بازاحه الثابت cast const away .. كيف يمكن عمل ذلك ؟

السطر الأخير وقد يبدوا غريب بعض الشيء .. نقوم بشرحه :
const_cast<char&>( تعني أن مابين القوسين سوف نزيح منه const ) ، اي حذف الـ const
static_cast<const TextBlock&>(*this) هنا اضافه const الى الكائن الحالي *this
[position] هنا سوف يتم استدعاء الداله ذات const

هذا هو الحل الأخر ، وبشكل عام عمليات Casting هي حلول سيئه في سي++ وعلينا أن نتجنبها بأي طريقه .. لكن بالمقابل تكرار الكود code duplication أمر لا يمكن السكوت عنه نهائيا .. لذلك اما أن تستخدم داله أخرى ، أو تستخدم الCasting .. 

 

أخيرا ، أستخدام const أمر جميل حقا ، فهو يضمن لك بأن المترجم سوف ينبهك بأي عمليه خاطئه لا تود أن يقع فيها عميل طبقتك … لذلك حاول أن تكثر من أستخدامها في اي مكان .. سوف تكون حقا مسرور بذلك  .

 

المصادر :
Effective c++
Thinking in C++

(59)

وجدي عصام مهندس برمجيات مهتم بعلوم الحاسب وبالأخص مجال الخوارزميات وهندسة البرمجيات وحماية التطبيقات،

LEAVE YOUR COMMENT

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

مشاركة