شرطات التسطير في لغة C++: قواعد غير واضحة تفسد الكود

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

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

ابدأ الدورة

شرطات التسطير في C++: القتلة الصامتون لرمزك

\n\n

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

\n\n

في أغلب الأحيان، يكمن جذر الشر في رموز شرطة التسطير (_) التي تبدو غير ضارة. هذا الموضوع هو "مقبرة" حقيقية للمبتدئين وحتى مطوري C++ ذوي الخبرة. ليس من قبيل الصدفة أن سؤال Stack Overflow حول قواعد استخدام شرطات التسطير حصل على عشرات الآلاف من المشاهدات. دعونا نحدد مرة واحدة وإلى الأبد ما هو مسموح، وما هو غير مسموح، وكيف لا نقع في فخ السلوك غير المحدد (UB).

\n\n

لماذا معيار C++ صارم جدًا تجاه شرطات التسطير؟

\n\n

جذر المشكلة هو التوافق ومساحة الأسماء العامة. تستخدم المكتبة القياسية لـ C++ شرطات التسطير بنشاط لاحتياجاتها الداخلية. إذا قمت عن طريق الخطأ بإنشاء معرف يتعارض مع الأسماء الداخلية للمكتبة أو المترجم، فقد انتهى الأمر. يمكن للمترجم بصمت إعادة تعريف متغيرك، وستحصل على سلوك غير محدد (Undefined Behavior) سيظهر في أكثر اللحظات غير المناسبة.

\n\n

يحدد معيار C++ (بدءًا من C++03 وفي جميع الإصدارات اللاحقة، بما في ذلك C++20 و C++23) بوضوح أي المعرفات هي محجوزة للتنفيذ. انتهاك هذه القواعد يجعل كودك ليس فقط غير صحيح، بل غير قابل للنقل.

\n\n

أفضل 5 أخطاء قاتلة مع شرطات التسطير

\n\n

دعنا نحلل أشهر المشاكل التي يقع فيها حتى المبرمجون المتمرسون.

\n\n

الخطأ رقم 1: شرطة التسطير المزدوجة في أي مكان

\n\n

القاعدة: أي معرف يحتوي على رمزين متتاليين لشرطة التسطير (__) في أي مكان، فهو محجوز للتنفيذ.

\n\n
// سيء! محجوز لـ "التنفيذ"\nint __myVariable = 42;\nvoid my__function();\nclass My__Class {};\n\n// جيد\nint myVariable = 42;\nvoid my_function();\nclass MyClass {};\n
\n\n

لماذا هذا خطير؟ يمكن للمترجم استخدام __myVariable لوحدات الماكرو الداخلية الخاصة به. على سبيل المثال، العديد من المترجمات تعرّف __LINE__ أو __cplusplus. إذا قمت بإنشاء __LINE__ الخاص بك، فقد يتجاهل المترجم تعريفك ببساطة، أو الأسوأ من ذلك، يستبدله بقيمته الخاصة. النتيجة هي سلوك غير محدد (UB).

\n\n

الخطأ رقم 2: شرطة التسطير + حرف كبير في النطاق العام

\n\n

القاعدة: المعرف الذي يبدأ بشرطة تسطير يتبعها حرف كبير (A-Z) محجوز للتنفيذ في أي نطاق رؤية.

\n\n
// سيء! حتى في النطاق المحلي!\nvoid foo() {\n    int _MyVar = 10; // انتهاك للقاعدة\n}\n\n// سيء! في النطاق العام - بالأحرى\nint _GlobalVar = 20;\n\n// جيد\nvoid foo() {\n    int myVar = 10;\n}\n
\n\n

هذه القاعدة هي الأكثر غدرًا. يعتقد الكثيرون: "حسنًا، أنا داخل دالة، من يهتم؟". يهتم! المعيار لا يقدم استثناءات للنطاق المحلي إذا كان المعرف يبدأ بشرطة تسطير وحرف كبير. يحق للمترجم تمامًا حجز هذه الأسماء لأغراضه الخاصة.

\n\n

الخطأ رقم 3: شرطة التسطير في البداية + حرف صغير في النطاق العام

\n\n

القاعدة: المعرف الذي يبدأ بشرطة تسطير يتبعها حرف صغير (a-z) محجوز للتنفيذ في النطاق العام (namespace scope).

\n\n
// سيء! في مساحة الأسماء العامة\nint _myGlobal = 0;\nvoid _helperFunction();\n\n// جيد - داخل دالة أو كلاس\nclass MyClass {\n    int _memberVar; // داخل الكلاس - مسموح (لكن الأفضل m_)\n};\n\nvoid foo() {\n    int _localVar = 5; // داخل الدالة                

المدونات

توصيات الكتب