قالب (C++)
القوالب هي ميزة في لغة البرمجة C++ تسمح للدوال والفئات بالعمل مع الأنواع العامة . وهذا يُمكّن تعريف الدالة أو الفئة من الإشارة، عبر متغير عام، إلى فئة أخرى مختلفة (سواء كانت فئة مضمنة أو نوع بيانات مُعلن حديثًا ) دون الحاجة إلى إنشاء تعريف كامل لكل فئة من هذه الفئات المختلفة.
ببساطة، يُعادل استخدام الصنف أو الدالة المُنمّطة (قبل عملية التجميع) نسخ ولصق كتلة الكود المُنمّطة في المكان المُستخدم فيه، ثم استبدال مُعامل القالب بالمعامل الفعلي. لهذا السبب، تُوضع تفاصيل تنفيذ الدوال المُنمّطة في ملفات الرأس ( *.h ) في الأصناف التي تستخدمها، إذ لا يُمكن تجميع أي رمز دون معرفة نوعه مُسبقًا.
توفر مكتبة C ++ القياسية العديد من الوظائف المفيدة ضمن إطار عمل من القوالب المتصلة.
كانت المصادر الرئيسية لإلهام قوالب C++ هي الوحدات النمطية ذات المعلمات التي توفرها لغة CLU والأنواع العامة التي توفرها لغة Ada . [ 1 ]
نظرة عامة فنية
توجد ثلاثة أنواع من القوالب: قوالب الدوال ، وقوالب الأصناف ، وقوالب المتغيرات (منذ C++14) . ومنذ C++11 ، يمكن أن تكون القوالب إما متغيرة الوسائط أو غير متغيرة الوسائط؛ أما في الإصدارات السابقة من C++، فهي دائمًا غير متغيرة الوسائط.
قوالب لغة C++ كاملة تورينج . [ 2 ]
قوالب الوظائف
يتصرف قالب الدالة كدالة عادية، باستثناء أنه يقبل وسائط من أنواع مختلفة، مما يتيح سلوكًا عامًا للأنواع (انظر المثال). بعبارة أخرى، يمثل قالب الدالة مجموعة من الدوال. صيغة تعريف قوالب الدوال ذات المعاملات النوعية هي:
قالب < مُعرِّف الفئة > الإعلان ؛ قالب < مُعرِّف النوع > الإعلان ؛كلا التعبيرين لهما نفس المعنى ويتصرفان بنفس الطريقة تمامًا. وقد تم تقديم الصيغة الأخيرة لتجنب الالتباس، [ 3 ] حيث لا يشترط أن يكون مُعامل النوع فئةً حتى إصدار C++20. (يمكن أن يكون نوعًا أساسيًا مثل intأو double.)
على سبيل المثال، تحتوي مكتبة C++ القياسية على قالب الدالة max(x, y)الذي يُرجع القيمة الأكبر من بين xو y. ويمكن تعريف قالب الدالة هذا على النحو التالي [ 4 ] :
template < typename T > [[ nodiscard ]] constexpr T & max ( const T & a , const T & b ) noexcept { return a < b ? b : a ; }يعمل تعريف هذه الدالة الواحدة مع أنواع بيانات متعددة. تحديدًا، تعمل مع جميع أنواع البيانات التي يُعرَّف لها عامل المقارنة (<bool ) وتُرجع قيمة من نوع قابل للتحويل . يُوفر استخدام قالب الدالة مساحة في ملف الشيفرة المصدرية، بالإضافة إلى حصر التغييرات في وصف دالة واحدة، مما يجعل الشيفرة أسهل للقراءة.
عادةً ما يُنتج قالب الدالة المُنشأ نفس كود الكائن، مقارنةً بكتابة دوال منفصلة لجميع أنواع البيانات المختلفة المُستخدمة في برنامج مُحدد. على سبيل المثال، إذا استخدم برنامج ما نسختين من قالب الدالة المذكور أعلاه، إحداهما للتعامل مع intالوسائط doubleوالأخرى max()للتعامل مع الوسائط الأخرى، فسيُنشئ المُصرّفmax() نسخة من كود الكائن تعمل على الوسائط `. سيكون مُخرجات المُصرّف مُطابقة لما كان سيُنتج لو احتوى الكود المصدري على نسختين منفصلتين غير مُنشأتين من قالب الدالة ` ، إحداهما مكتوبة للتعامل مع الوسائط `.` والأخرى مكتوبة للتعامل مع الوسائط ` .`.intdoublemax()intdouble
إليك كيفية استخدام قالب الدالة:
استيراد std ؛int main () { // سيتم استدعاء الدالة max<int> عن طريق استنتاج الوسيط الضمني. std :: println ( "{}" , std :: max ( 3 , 7 ));// سيتم استدعاء الدالة max<double> عن طريق استنتاج الوسيط الضمني. std :: println ( "{}" , std :: max ( 3.0 , 7.0 ));// نحتاج إلى تحديد نوع الوسائط بشكل صريح؛ // على الرغم من أن std::type_identity يمكن أن يحل هذه المشكلة... std :: println ( "{}" , max <double> ( 3 , 7.0 ) ); }في الحالتين الأوليين، Tيستنتج المترجم تلقائيًا وسيط القالب ليكون ` intx` و`y` doubleعلى التوالي. أما في الحالة الثالثة، max(3, 7.0)فسيفشل الاستنتاج التلقائي لـ `x` لأن نوع المعاملات يجب أن يتطابق تمامًا مع وسائط القالب. لذلك، نقوم بإنشاء نسخة صريحة من `x` doubleباستخدام `x` max<double>().
يمكن إنشاء نسخة من قالب الدالة هذا باستخدام أي نوع قابل للنسخ يكون التعبير صالحًا له. بالنسبة للأنواع المعرفة من قبل المستخدم، يعني هذا أنه يجب تحميل عامل المقارنة (أصغر من ) بشكل زائد في النوع.y < x<
قوالب الوظائف المختصرة
منذ إصدار C++20 ، عند استخدام auto`or` concept autoفي أي من معلمات تعريف الدالة ، يصبح هذا التعريف تعريفًا مختصرًا لقالب الدالة . [ 5 ] يُعرّف هذا التعريف قالب دالة، ويُضاف مُعامل قالب مُبتكر واحد لكل عنصر نائب إلى قائمة مُعاملات القالب.
// ما يعادل: // قالب <typename T> // void f1(T x); void f1 ( auto x );// مكافئ لـ (إذا كان Concept1 مفهومًا): // <Concept1 T> // void f2(T x); void f2 ( Concept1 auto x );// مكافئ لـ (إذا كان Concept2 مفهومًا): // قالب <Concept2... Ts> // void f3(Ts... xs) void f3 ( Concept2 auto ... xs );// مكافئ لـ (إذا كان Concept2 مفهومًا): // قالب <Concept2 T> // void f4(T... xs); void f4 ( Concept2 auto xs , ...);// مكافئ لـ (إذا كان Concept3 و Concept4 مفاهيم): // قالب <Concept3 T, Concept4 U> // f5(const T* t, U& u); void f5 ( const Concept3 auto * t , Concept4 auto & u );قد يبدو تقييد max()استخدام المفاهيم على النحو التالي:
باستخدام std :: total_ordered ;// في تعريف اسم النوع: template < totally_ordered T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }// في عبارة requires: template < typename T > requires totally_ordered < T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }قوالب الفئات
يُوفّر قالب الفئة مواصفات لإنشاء فئات بناءً على مُعاملات. تُستخدم قوالب الفئات عمومًا لتنفيذ الحاويات . يتم إنشاء نسخة من قالب الفئة بتمرير مجموعة مُحددة من الأنواع إليه كوسائط للقالب. [ 6 ] تحتوي مكتبة C++ القياسية على العديد من قوالب الفئات، ولا سيما الحاويات المُقتبسة من مكتبة القوالب القياسية ، مثل vector...
قوالب المتغيرات
في لغة C++14، يمكن استخدام القوالب أيضًا للمتغيرات، كما في المثال التالي:
template < typename T > constexpr T PI = T { 3.141592653589793238462643383L }; // (تقريبًا) من std::numbers::piمعلمات القوالب غير النوعية
على الرغم من أن استخدام القوالب على الأنواع، كما في الأمثلة أعلاه، هو الشكل الأكثر شيوعًا للقوالب في لغة C++، إلا أنه من الممكن أيضًا استخدام القوالب على القيم. وبالتالي، على سبيل المثال، فئة مُعلنة باستخدام
قالب < int K > فئة MyClass ;يمكن إنشاء مثيل له باستخدام قيمة محددة int.
كمثال عملي، يعتمد نوع المصفوفة ذات الحجم الثابت في المكتبة القياسية على قالبين: نوع (يمثل نوع الكائن الذي تحتويه المصفوفة) وعدد (يمثل عدد عناصر المصفوفة). لإنشاء فئة مكافئة ، يمكن تعريفها كما يلي:std::arraystd::size_tArraystd::array
template < class T , size_t N > struct Array ;charويمكن الإعلان عن مصفوفة من ستة عناصر:
Array < char , 6 > myArray ;تخصص القالب
عندما يتم إنشاء دالة أو فئة من قالب، يقوم المترجم بإنشاء تخصص لهذا القالب لمجموعة الوسائط المستخدمة، ويشار إلى هذا التخصص بأنه تخصص تم إنشاؤه.
تخصيص القوالب الصريح
قد يقرر المبرمج أحيانًا تنفيذ نسخة خاصة من دالة (أو فئة) لمجموعة معينة من وسائط نوع القالب، وهو ما يُسمى بالتخصيص الصريح. وبهذه الطريقة، يمكن لبعض أنواع القوالب أن تمتلك تنفيذًا متخصصًا مُحسَّنًا لهذا النوع، أو تنفيذًا أكثر فائدة من التنفيذ العام.
- إذا تم تخصيص قالب فئة بواسطة مجموعة فرعية من معلماته، فإنه يسمى تخصيص القالب الجزئي (لا يمكن تخصيص قوالب الدوال جزئيًا).
- إذا تم تخصيص جميع المعلمات، فهذا يُعد تخصيصًا كاملاً .
يُستخدم التخصيص الصريح عندما يجب أن يختلف سلوك دالة أو فئة ما، عند اختيار معلمات القالب بشكل محدد، عن السلوك العام: أي عن الكود المُولّد بواسطة القالب الرئيسي أو القوالب. على سبيل المثال، يُعرّف تعريف القالب أدناه تطبيقًا خاصًا لـ ` max()for arguments of type` const char*.
استيراد std ؛template <> [[ nodiscard ]] constexpr const char * max ( const char * a , const char * b ) noexcept { // عادةً، تكون نتيجة المقارنة المباشرة // بين سلسلتين نصيتين في لغة C سلوكًا غير مُعرَّف؛ // استخدام std::strcmp يجعلها مُعرَّفة. return std :: strcmp ( a , b ) > 0 ? a : b ; }قوالب متغيرة
قدمت لغة C++11 القوالب المتغيرة ، والتي يمكنها أن تأخذ عددًا متغيرًا من الوسائط بطريقة مشابهة إلى حد ما للوظائف المتغيرة مثل std::printf.
باستخدام std :: format_string ؛ باستخدام std :: ofstream ؛enum class Level { ... };ofstream logFile { "logfile.txt" };template < typename ... Args > void log ( const format_string < Args ... >& fmt , Args && ... args ) { std :: println ( logFile , fmt , args ...); }نظرًا لأن لغة C++ تدعم فقط المعاملات المتغيرة على نمط C، فإن الطريقة الوحيدة للحصول على وظائف متغيرة آمنة من حيث النوع (كما هو الحال في Java ) هي من خلال القوالب المتغيرة.
أسماء مستعارة للقوالب
قدمت لغة C++11 أسماء القوالب المستعارة، والتي تعمل مثل تعريفات الأنواع ذات المعاملات .
يوضح الكود التالي إعادة تسمية الملفات std::mapإلى TreeMapو std::unordered_mapإلى HashMap، بالإضافة إلى إنشاء اسم بديل StringHashMapلـ std::unordered_map<K, std::string>. وهذا يسمح، على سبيل المثال، StringHashMap<int>باستخدام كاختصار لـ std::unordered_map<int, std::string>.
باستخدام String = std :: string ;// السماح بالتخصيص الاختياري لدوال التجزئة، والمخصصات، وما إلى ذلك. template < typename K , typename V , typename Compare = std :: less < K > , typename Alloc = std :: allocator < std :: pair < const K , T >> > using TreeMap = std :: map < K , V , Compare , Alloc > ;template < typename K , typename V , typename HashFn = std :: hash < K > , typename KeyEq = std :: equal_to < K > , typename Alloc = std :: allocator < std :: pair < const K , T >> > using HashMap = std :: unordered_map < K , V , HashFn , KeyEq , Alloc > ;// أو، السماح فقط بتخصيص K و V: template < typename K , typename V > using TreeMap = std :: map < K , V > ;template < typename K , typename V > using HashMap = std :: unordered_map < K , V > ;// تعريف StringHashMap<K> = HashMap<K, String> template < typename K > using StringHashMap = HashMap < K , String > ;StringHashMap <int> myMap = /* شيء ما هنا... * / ;القوالب المقيدة
منذ إصدار C++20 ، أصبح بالإمكان تقييد القوالب بشكل مشابه لتقييد الأحرف البديلة في Java أو C# و Rustwhere . ويتم ذلك باستخدام مفاهيم تمثل مجموعة من المسندات المنطقية التي يتم تقييمها في وقت الترجمة.
على سبيل المثال، يستخدم هذا الكود std::derived_from<Derived, Based>حدًا أعلى للوراثة. تُحقق الفئة هذا المفهوم إذا ورثت من Player، ولا يمكن استخدام الفئات التي لا ترث من كمعامل قالب في processListOfPlayers().
استيراد std ؛باستخدام std :: derived_from ؛ باستخدام std :: vector ؛class Player { // ... };// يجب أن يكون T نوعًا يكون الحد الأعلى للوراثة فيه هو Player، // مما يمنع أي نوع لا يرث من Player template < derived_from < Player > T > void processListOfPlayers ( const vector < T >& players ) { // ... }يمكن استخدام المفاهيم لتنفيذ شكل من أشكال المعاملات المتغيرة الآمنة من حيث النوع. على سبيل المثال، توقيع جافا<T>voidfn(T...args);
باستخدام std :: same_as ؛template < typename T > void fn ( same_as < T > auto ... args ) { // ... }علاوة على ذلك، لتقييد هذه التوقيعات بنوع محدد، يمكن استخدام مفهومي و . على سبيل المثال، يمكن كتابة توقيعات جافا و على النحو التالي: و على التوالي.std::same_as<T,U>std::convertible_to<T,U>voidfoo(int...args);voidbar(String...args);voidfoo(same_as<int>auto...args);voidbar(convertible_to<string>auto...args);
القوالب المصدرة
في C++03 ، أُضيفت "القوالب المُصدَّرة" إلى لغة C++. [ 7 ] ثم أُزيلت هذه الميزة لاحقًا في C++11 ، نظرًا لقلة عدد المُترجمات التي تدعمها. [ 8 ] وكان مُترجم Comeau C/C++ هو المُترجم الوحيد المعروف بدعمه للقوالب المُصدَّرة . ومن بين الأسباب التي ذُكرت لإزالتها:
- مكلف أو معقد التنفيذ
- فائدة ضئيلة لمعظم المستخدمين، فضلاً عن قلة الاهتمام.
- صعب الاستخدام
- تغييرات في معاني السمات اللغوية الحالية
- تقييد التطوير المستقبلي للغة C++
القالب المُصدَّر هو في الأساس قالب فئة تُصدَّر منه عناصر البيانات الثابتة والأساليب غير المضمنة. ويجب تمييزه بالكلمة المفتاحية export`exported`. ما يُميز القالب المُصدَّر هو أنه لا يحتاج إلى تعريف في وحدة ترجمة تستخدمه. [ 9 ] [ 10 ] على سبيل المثال (في C++03):
الملف 1.cpp :
#include <iostream>static void trace () { std :: cout << "الملف 1" << std :: endl ; }تصدير قالب < نوع T > T min ( ثابت T & x ، ثابت T & y );int main () { trace (); std :: cout << min ( 2 , 3 ) << std :: endl ; }الملف 2.cpp :
#include <iostream>static void trace () { std :: cout << "الملف 2" << std :: endl ; }export template < typename T > T min ( const T & x , const T & y ) { trace (); return a < b ? a : b ; }مع إدخال الوحدات النمطية في لغة C++20export ، أُعيدت إضافة الكلمة المفتاحية إلى لغة C++. وقد سمح هذا مجدداً بتصريحات مثل هذه:
استيراد std ؛باستخدام std :: is_base_of_v ;export class Atom { // ... };تصدير قالب < نوع T > مفهوم ExtendsAtom = is_base_of_v < Atom , T > ;export template < ExtendsAtom Instance , typename ... Bases > class Cluster : public Bases ... { private : Instance x ; public : explicit Cluster ( Instance x , Bases && ... bases ) : Bases ( bases )..., x { x } {}// ... };إن فوائد سرعة التجميع التي كان من المفترض أن توفرها القوالب المصدرة يتم توفيرها بواسطة الوحدات النمطية على أي حال، مما يجعل هذه الميزة قديمة بشكل أساسي وتم استبدالها بالوحدات النمطية.
ميزات البرمجة العامة في لغات أخرى
في البداية، لم يكن مفهوم القوالب مُدرجًا في بعض اللغات، مثل جافا وسي شارب 1.0. يُحاكي اعتماد جافا للأنواع العامة سلوك القوالب، ولكنه يختلف عنها تقنيًا. أضافت سي شارب الأنواع العامة (الأنواع المُعاملة) في .NET 2.0. وتسبق الأنواع العامة في لغة آدا قوالب سي ++.
على الرغم من أن قوالب C++، والأنواع العامة في Java، والأنواع العامة في .NET تُعتبر متشابهة في كثير من الأحيان، إلا أن الأنواع العامة تُحاكي فقط السلوك الأساسي لقوالب C++ . [ 11 ] بعض ميزات القوالب المتقدمة التي تستخدمها مكتبات مثل Boost و STLSoft ، وتطبيقات STL، لبرمجة القوالب الوصفية (التخصيص الصريح أو الجزئي، ومعاملات القوالب الافتراضية، ومعاملات القوالب غير النوعية، ومعاملات قوالب القوالب، ...) غير متوفرة مع الأنواع العامة.
في قوالب لغة C++، كانت حالات وقت الترجمة تُنفذ تاريخيًا عن طريق مطابقة الأنماط على وسائط القالب. على سبيل المثال، تم تنفيذ فئة القالب الأساسية في مثال حساب المضروب أدناه عن طريق مطابقة الصفر بدلاً من اختبار عدم المساواة، الذي لم يكن متاحًا سابقًا. ومع ذلك، std::conditionalفقد وفر ظهور ميزات المكتبة القياسية في C++11 طريقة أخرى أكثر مرونة للتعامل مع إنشاء القوالب المشروطة.
// قالب الاستقراء < unsigned int N > struct Factorial { static constexpr unsigned int value = N * Factorial < N - 1 >:: value ; };// الحالة الأساسية عبر تخصيص القالب: template <> struct Factorial < 0 > { static constexpr unsigned int value = 1 ; };باستخدام هذه التعريفات، يمكن للمرء أن يحسب، على سبيل المثال 6! في وقت الترجمة باستخدام التعبير Factorial<6>::value.
بدلاً من ذلك، يمكن استخدام constexprC++11 / if constexprC++17 لحساب هذه القيم مباشرة باستخدام دالة في وقت الترجمة:
template < unsigned int N > [[ nodiscard ]] constexpr unsigned int factorial () noexcept { if constexpr ( N <= 1 ) { return 1 ; } else { return N * factorial < N - 1 > (); } }ولهذا السبب، تُستخدم برمجة القوالب الوصفية الآن في الغالب لإجراء عمليات على الأنواع.
انظر أيضاً
مراجع
- ↑ ستروستروب، بيارن (8 سبتمبر 2004). "لغة البرمجة سي++" . Stroustrup.com (الصفحة الشخصية) (الطبعة الثالثة، طبعة خاصة).
- ↑ "قوالب لغة C++ كاملة تورينج" (ملف PDF) . ubietylab.net . مؤرشف من النسخة الأصلية (ملف PDF) بتاريخ 2013-11-01.
- ↑ ليبمان، ستان (11 أغسطس 2004). "لماذا تدعم لغة C++ كلاً من الفئة واسم النوع لمعاملات النوع" . شبكة مطوري مايكروسوفت (MSDN) .
- ↑ "std::max" . cppreference.com . تم الاطلاع عليه في 21 يوليو 2025 .
- ↑ "P1141R1 - نهج آخر للتصريحات المقيدة" . مؤرشف من الأصل بتاريخ 11-11-2018 . تم الاطلاع عليه بتاريخ 11-11-2018 .
- ^ فانديفورد ، ديفيد. جوسوتيس، نيكولاي (2002). قوالب C++: الدليل الكامل أديسون ويسلي . رقم ISBN 978-0-201-73484-3.
- ↑ "نموذج الفصل الدراسي" . cppreference.com . 7 أكتوبر 2025.
- ↑ هيرب ساتر (3 مارس 2003). "لماذا لا نستطيع تحمل تكاليف التصدير" (ملف PDF) . open-std.org . مجموعة العمل 21.
- ↑ "نظرة عامة على التصدير" . comeaucomputing.com . شركة كومو للحوسبة. مؤرشف من الأصل في 2 يونيو 2003.
- ↑ "نظرة عامة على القالب المُصدَّر" . comeaucomputing.com . شركة كومو للحوسبة. مؤرشف من الأصل بتاريخ 22 أبريل 2003.
- ↑ "الاختلافات بين قوالب C++ والأنواع العامة في C# (دليل برمجة C#)" . 12 مارس 2024.
روابط خارجية
- البرمجة العامة
- البرمجة الوصفية
- لغة سي++
