قوالب الوظائف وخصم النوع
تعلم كيفية كتابة الوظائف التي تعمل مع أي نوع. فهم استنتاج وسيطة القالب، والمعلمات غير النوعية، وسبب وجود القوالب في الرؤوس.
لماذا قوالب الوظائف؟
بدون القوالب، كتابة دالة تعمل لأنواع متعددة يعني تكرار المنطق. ستكتبint max(int, int)، وdouble max(double, double)، وstd::string max(std::string, std::string)— منطق متطابق يتكرر لكل نوع. قوالب الوظائف تتيح لك كتابة الخوارزمية مرة واحدة والسماح للمترجم بإنشاء إصدارات خاصة بالنوع تلقائيًا.
قالب الوظيفة ليس وظيفة — بل هو مخطط يقوم المترجم بإنشاء مثيل منه للوظائف الفعلية عند استخدامها مع أنواع محددة. يحدث هذا بالكامل في وقت الترجمة، مما يؤدي إلى إنتاج تعليمات برمجية مطابقة لما تكتبه يدويًا - بدون أي تكاليف إضافية.
بناء جملة القالب
يتم تقديم قالب الوظيفة باستخدام الكلمة الأساسية template متبوعة بقائمة المعلمات بين قوسين زاوية. يمكنك استخدام typename أو class بالتبادل لمعلمات النوع - فهي تعني نفس الشيء تمامًا. الاتفاقية الحديثة تفضلtypename.
#include <iostream>
#include <string>
// 'typename' and 'class' are interchangeable here
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Equivalent — 'class' means the same thing
template <class T>
T minimum(T a, T b) {
return (a < b) ? a : b;
}
int main() {
std::cout << maximum(3, 7) << '\n'; // deduces T = int
std::cout << maximum(3.14, 2.71) << '\n'; // deduces T = double
std::cout << maximum<std::string>("hello", "world") << '\n';
// explicit instantiation needed — "hello" is const char*, not std::string
}قواعد خصم حجة القالب
يستنتج المترجم وسيطات القالب عن طريق مطابقة أنواع وسيطات الوظيفة مع أنماط المعلمات. القواعد الأساسية هي:
1. يفضل المطابقة التامة — يستنتجmaximum(3, 7)T = intلأن كلا الوسيطتين هماint.
2. لا توجد تحويلات ضمنية أثناء الاستقطاع —maximum(3, 7.0)هو خطأ: يرى المترجمT = intمن الوسيطة الأولى وT = doubleمن الوسيطة الثانية، ويرفض اختيار واحد.
3. تسوس الصفيف إلى المؤشر — عندما يظهرTكمعلمة قيمة، تتحلل المصفوفات إلى مؤشرات. استخدمT(&)[N]لالتقاط نوع المصفوفة وحجمها.
4. يتم تجريد const/volatile للمستوى الأعلى من معلمات القيمة ولكن يتم الاحتفاظ بها للمعلمات المرجعية/المؤشر.
5. طي المرجع — إذا تم استنتاجTكنوع مرجعي (في إعادة توجيه المراجع)، فسيتم تطبيق قواعد طي المرجع:T& &يصبحT&،T& &&يصبحT&،T&& &يصبحT&،T&& &&يصبحT&&.
عندما يفشل الخصم أو يكون غامضًا، يجب عليك توفير وسيطات القالب بشكل صريح:maximum.
معلمات القالب غير النوعية
لا تقتصر معلمات القالب على الأنواع. معلمات القالب غير النوعية (NTTPs) تقبل قيم وقت الترجمة مثل الأعداد الصحيحة، أو المؤشرات، أو (منذ C++20) أنواع الفاصلة العائمة والفئات. إنها قوية لتضمين ثوابت وقت الترجمة في نظام الكتابة.
#include <iostream>
#include <array>
#include <cstddef>
// Non-type parameter: N is a compile-time size_t value
template <typename T, std::size_t N>
T sum(const std::array<T, N>& arr) {
T total{};
for (const auto& elem : arr) {
total += elem;
}
return total;
}
// C++20: auto non-type parameter accepts any structural type
template <auto Value>
constexpr auto doubled = Value * 2;
int main() {
std::array<int, 4> nums = {1, 2, 3, 4};
std::cout << sum(nums) << '\n'; // 10, deduces T=int, N=4
std::cout << doubled<21> << '\n'; // 42
std::cout << doubled<3.14> << '\n'; // 6.28 (C++20)
}تؤدي كل مجموعة فريدة من وسيطات القالب إلى قيام المترجم بإنشاء إنشاء مثيل منفصل — نسخة مميزة من الوظيفة في ملفك الثنائي. يؤدي استدعاءmaximumوmaximumوmaximumإلى إنتاج ثلاث وظائف. عادةً ما يكون هذا أمرًا جيدًا، ولكن في قواعد التعليمات البرمجية الكبيرة، يمكن أن تساهم القوالب في تضخم التعليمات البرمجية (ثنائيات أكبر، وأوقات ترجمة أطول). تتضمن استراتيجيات التخفيف ما يلي: استخدام إعلانات القالب الخارجي للتحكم في مكان حدوث عمليات إنشاء مثيلات، وإخراج التعليمات البرمجية المستقلة عن النوع من القوالب، واستخدام محو النوع عندما يكون تعدد الأشكال في وقت التشغيل مقبولاً.
نظرًا لأن المترجم يحتاج إلى رؤية تعريف القالب الكامل لإنشاء مثيل له، فلا يمكن تقسيم القوالب إلى ملفات.h/.cppبالطريقة التي يمكن بها للوظائف العادية. إذا قمت بوضع تعريف قالب في ملف.cppوحاولت استخدامه من وحدة ترجمة أخرى، فسوف تحصل على خطأ في الرابط (مرجع غير محدد). الحل القياسي هو وضع تعريف القالب بالكامل في ملف الرأس. إذا أصبحت أوقات الترجمة مشكلة، فيمكنك استخدام الإنشاء الصريح: ضع التعريف في ملف.cppوأضفtemplate int maximumلإنشاء مثيل للتخصصات التي تحتاجها بشكل صريح.
إنشاء مثيل صريح
يمكنك التحكم بوضوح في مكان وزمان إنشاء مثيل للقوالب. يمكن أن يؤدي ذلك إلى تقليل أوقات الترجمة في المشاريع الكبيرة عن طريق منع عمليات إنشاء مثيلات زائدة عن الحاجة عبر وحدات الترجمة.
// ---- maximum.h ----
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Suppress implicit instantiation in every TU that includes this header
extern template int maximum<int>(int, int);
extern template double maximum<double>(double, double);
// ---- maximum.cpp ----
#include "maximum.h"
// Explicit instantiation: generate code here, once
template int maximum<int>(int, int);
template double maximum<double>(double, double);- قوالب الوظائف عبارة عن مخططات - يقوم المترجم بإنشاء وظائف محددة لكل مجموعة من وسائط القالب
typenameوclassقابلان للتبديل في قوائم معلمات القالب- لا يؤدي خصم وسيطة القالب إلى إجراء تحويلات ضمنية — فالاستقطاعات الغامضة هي أخطاء
- تقوم معلمات القالب غير النوعية بتضمين قيم وقت الترجمة في نظام الكتابة
- يجب تعريف القوالب في الرؤوس (أو استخدام إنشاء مثيل صريح) لأن المترجم يحتاج إلى التعريف الكامل لإنشاء مثيل
اختبار — اختبر معرفتك
(15 نقطة خبرة)1. ماذا يحدث عند الاتصال بـ`maximum(3, 7.0)`باستخدام القالب`template<typename T> T maximum(T a, T b)`؟
2. لماذا يجب عادةً وضع تعريفات القالب في ملفات الرأس؟
3. ما هو الغرض من`extern template int maximum<int>(int, int);`؟