explicit في C++: شرح كامل مع أمثلة برمجية

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

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

ابدأ الدورة

ما الذي تخفيه الكلمة المفتاحية explicit في C++؟ تحليل كامل مع أمثلة

\n\n

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

\n\n

في هذه المقالة، سنشرح ما هو explicit، ولماذا تحتاجه، وكيف تستخدمه، والأهم من ذلك — كيف ينقذك من أخطاء يصعب العثور عليها. سننتقل من البسيط إلى المعقد: نبدأ بمثال أساسي، وننتهي بسيناريوهات حقيقية من بيئات الإنتاج. هيا بنا!

\n\n

المشكلة: المنشئات الضمنية — شر أم خير؟

\n\n

تخيل أن لديك كلاسًا بسيطًا للتعامل مع الكسور:

\n\n
class Fraction {\npublic:\n    int numerator;\n    int denominator;\n\n    // منشئ بمعامل واحد\n    Fraction(int num) : numerator(num), denominator(1) {}\n};
\n\n

يبدو الأمر غير ضار، أليس كذلك؟ الآن لنكتب دالة تستقبل كسرًا:

\n\n
void printFraction(const Fraction& f) {\n    std::cout << f.numerator << "/" << f.denominator << std::endl;\n}
\n\n

والآن — انتبه! — اللحظة الأكثر خطورة:

\n\n
printFraction(5); // ؟؟؟\n
\n\n

سيقوم المترجم بترجمة هذا الكود دون أي خطأ. لماذا؟ لأنه سيقوم ضمنيًا باستدعاء المنشئ Fraction(int) وتحويل الرقم 5 إلى كائن Fraction(5/1). للوهلة الأولى، هذا مريح. لكن تخيل أنك مررت رقمًا عن طريق الخطأ إلى مكان كان يتوقع كائنًا، وأصبح البرنامج يتصرف بشكل غريب. تصحيح مثل هذا الخطأ قد يستغرق ساعات.

\n\n

إليك مثالًا أوضح — تخيل كلاسًا للتعامل مع كلمة المرور:

\n\n
class Password {\npublic:\n    Password(const std::string& str) : value(str) {}\n    bool isValid() const { return value.length() >= 8; }\nprivate:\n    std::string value;\n};
\n\n

تخيل الآن دالة:

\n\n
void checkPassword(const Password& p) {\n    if (p.isValid()) {\n        std::cout << "كلمة المرور قوية" << std::endl;\n    } else {\n        std::cout << "كلمة المرور قصيرة جدًا" << std::endl;\n    }\n}
\n\n

واستدعاء:

\n\n
checkPassword("12345"); // انتبه!\n
\n\n

سيقوم المترجم بصمت بإنشاء كائن Password من النص "12345"، وستعمل الدالة. لكن لماذا؟ هل أردنا حقًا أن يتحول أي نص تلقائيًا إلى كلمة مرور؟ بالتأكيد لا. وهنا يأتي دور explicit.

\n\n

الحل: الكلمة المفتاحية explicit

\n\n

تُوضع الكلمة المفتاحية explicit قبل المنشئ وتمنع التحويلات الضمنية. الآن، إذا حاول أي شخص تمرير رقم أو نص إلى مكان يتوقع كائنًا من الكلاس الخاص بك، سيعطي المترجم خطأ. دعنا نعيد كتابة الكلاس Fraction:

\n\n
class Fraction {\npublic:\n    int numerator;\n    int denominator;\n\n    explicit Fraction(int num) : numerator(num), denominator(1) {}\n};
\n\n

الآن، هذا الكود لن يتم ترجمته:

\n\n
printFraction(5); // خطأ في الترجمة!\n
\n\n

لكن لا يزال بإمكانك إنشاء الكائن بشكل صريح:

\n\n
printFraction(Fraction(5)); // يعمل بشكل طبيعي\nprintFraction(static_cast<Fraction>(5)); // يعمل أيضًا\n
\n\n

أي أن explicit لا يمنع إنشاء الكائن — بل يمنع الإنشاء التلقائي الضمني. إنه مثل باب عليه لافتة "لأصحاب التصاريح فقط": يمكنك الدخول، ولكن فقط إذا أظهرت هويتك.

\n\n

متى تستخدم explicit؟ توصيات عملية

\n\n

القاعدة الذهبية

المدونات

توصيات الكتب