مقدمة: المعركة الأبدية للثوابت في لغة C++
\n\nإذا كنت قد كتبت بلغة C++ من قبل (أو حتى بدأت في تعلمها)، فمن المؤكد أنك صادفت const. هذه البادئة السحرية تعد بأن قيمة المتغير لن تتغير. ولكن بعد ذلك جاء معيار C++11 وأحضر معه constexpr. وبدأ الارتباك.
على موقع Stack Overflow، جمع سؤال "ما الفرق بين constexpr و const؟" مئات الآلاف من المشاهدات. يخلط المبرمجون من جميع المستويات بين هذين المفهومين، مستخدمين إياهما كمرادفين. لكن هذا خطأ كبير!
\n\nفي هذا البرنامج التعليمي، سنفهم مرة واحدة وإلى الأبد كيف يختلف constexpr عن const في لغة C++. ستتعلم:
- \n
- ما هو الثابت في وقت الترجمة والثابت في وقت التنفيذ. \n
- لماذا
constلا يتعلق دائمًا بالأداء. \n - كيف يحول
constexprلغة C++ إلى لغة حديثة وآمنة. \n - متى يجب استخدام كل من هذين البناءين. \n
هيا بنا! 🚀
\n\nالفصل 1. const: وعد وليس ضمانًا
\n\nلنبدأ من الأساسيات. const في لغة C++ هو مؤهل نوع يخبر المترجم: "مرحبًا، لا يمكن تغيير هذه القيمة بعد التهيئة". يبدو الأمر بسيطًا، ولكن هناك فارق بسيط.
مثال 1: const قيد التشغيل
\n\n#include <iostream>\n\nint main() {\n const int x = 42; // وعد: x لن يتغير\n // x = 100; // خطأ في الترجمة — رائع!\n \n std::cout << x; // سيطبع 42\n return 0;\n}\n\nكل شيء جميل. ولكن ماذا لو تم حساب قيمة المتغير const فقط أثناء وقت التنفيذ؟
مثال 2: const يتم حسابه في وقت التشغيل
\n\n#include <iostream>\n\nint get_value() {\n return 42; // الدالة ترجع قيمة\n}\n\nint main() {\n const int y = get_value(); // هل لا يزال const؟\n // لكن قيمة y معروفة فقط أثناء وقت التنفيذ!\n \n // لنحاول استخدامه في مصفوفة:\n // int arr[y]; // خطأ! y ليس ثابتًا في وقت الترجمة!\n \n return 0;\n}\n\nها هو، الخدعة الرئيسية! const يحمي المتغير من التغيير، لكنه لا يضمن أن قيمته معروفة في مرحلة الترجمة. إذا تم الحصول على القيمة من دالة، فسيتم حسابها فقط عند تشغيل البرنامج.
الاستنتاج الرئيسي: const هو عقد على عدم القابلية للتغيير، وليس على قابلية الحساب في وقت الترجمة.
الفصل 2. constexpr: الثوابت التي تعيش في الكود
\n\nهنا يظهر constexpr على المسرح. ظهر هذا المحدد في معيار C++11 وغيّر بشكل جذري النهج تجاه الثوابت. constexpr يعني: "يجب حساب هذه القيمة أثناء وقت الترجمة". أو، على الأقل، يمكن حسابها.
مثال 3: متغير constexpr
\n\n#include <iostream>\n\nint main() {\n constexpr int z = 42; // ضمان: القيمة معروفة في وقت الترجمة\n \n int arr[z]; // يعمل! z هو ثابت وقت الترجمة\n \n std::cout << sizeof(arr) / sizeof(arr[0]); // سيطبع 42\n return 0;\n}\n\nالفرق واضح: constexpr يسمح باستخدام المتغير في الأماكن التي يحتاج فيها المترجم إلى معرفة القيمة الدقيقة — على سبيل المثال، لحجم مصفوفة أو معاملات القوالب.
مثال 4: دوال constexpr
\n\nلكن الشيء الأكثر إثارة للاهتمام هو الدوال. يمكن تطبيق constexpr على الدوال، وبالتالي يمكن حسابها أثناء وقت الترجمة، إذا كانت جميع الوسائط أيضًا ثوابت وقت الترجمة.
#include <iostream>\n\nconstexpr int square(int x) {\n return x * x;\n}\n\nint main() {\n constexpr int result = square(5); // يتم حسابه في وقت الترجمة!\n int arr[result]; // يعمل\n \n std::cout << result; // 25\n \n int runtime_value =