تخصيص القوالب
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 66 من 104.
أحياناً لا يعمل القالب العام (generic template) بشكل جيد مع كل الأنواع. على سبيل المثال، مقارنة سلاسل النصوص بنمط C (C-style strings) باستخدام > تقارن عناوين المؤشرات، وليس النص الفعلي. يتيح لك تخصيص القالب (Template specialization) توفير تنفيذ مخصص لأنواع معينة مع الاحتفاظ بالإصدار العام لكل شيء آخر.
يحل التخصيص الكامل محل القالب بالكامل لنوع واحد محدد. تقوم بالتصريح عنه باستخدام قائمة معلمات قالب فارغة وتحدد النوع المحدد:
// القالب الأساسي
template <typename T>
class Printer {
public:
void print(T value) {
std::cout << value << std::endl;
}
};
// تخصيص كامل لـ const char*
template <>
class Printer<const char*> {
public:
void print(const char* value) {
std::cout << "String: " << value << std::endl;
}
};
Printer<int> intPrinter;
intPrinter.print(42); // المخرجات: 42
Printer<const char*> strPrinter;
strPrinter.print("hello"); // المخرجات: String: helloيمكن أيضًا تخصيص قوالب الدوال:
template <typename T>
bool isEqual(T a, T b) {
return a == b;
}
template <>
bool isEqual<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) == 0;
}
isEqual(5, 5); // يستخدم الإصدار العام
isEqual("hi", "hi"); // يستخدم الإصدار المخصصيفضل المترجم دائماً التطابق الأكثر تحديداً - فإذا وجد تخصيص للنوع الدقيق المستخدم، فسيتم اختياره بدلاً من القالب العام.
التحدي
سهللنقم ببناء نظام منسق (formatter) مدرك للأنواع يوضح كيف يتيح لك تخصيص القوالب (template specialization) تخصيص السلوك لأنواع معينة مع الحفاظ على سلوك عام احتياطي لكل شيء آخر.
ستقوم بإنشاء ملفين لتنظيم كود القوالب الخاص بك:
Formatter.h: قم بتعريف قالب فئة (class template) يسمىFormatterيقوم بتنسيق القيم للعرض. يجب أن يعمل القالب العام الخاص بك مع أي نوع، ولكنك ستنشئ أيضاً إصدارات متخصصة للأنواع التي تحتاج إلى معالجة مخصصة.يجب أن يحتوي قالب
Formatterالأساسي على:- دالة
format()تأخذ قيمة من النوع T وتطبع:Value: <value>
قم بإنشاء تخصيص كامل (full specialization) للنوع
boolيطبع نصاً قابلاً للقراءة بدلاً من 1 أو 0:- يجب أن تطبع دالة
format():Boolean: trueأوBoolean: false
قم بإنشاء تخصيص كامل للنوع
const char*يضيف علامات اقتباس حول النصوص:- يجب أن تطبع دالة
format():String: "<value>"
قم أيضاً بإنشاء قالب دالة (function template) يسمى
formatPairيأخذ قيمتين من نفس النوع ويطبعهما معاً كـ:Pair: [<first>, <second>]قم بإنشاء تخصيص لـ
formatPairللنوعconst char*يغلف كل نص بعلامات اقتباس:Pair: ["<first>", "<second>"]- دالة
main.cpp: اقرأ أربعة مدخلات (كل منها في سطر منفصل):- عدد صحيح (integer)
- عدد عشري (double)
- قيمة منطقية كنص (
trueأوfalse) - قيمة نصية (string)
قم باستعراض المنسقات الخاصة بك عن طريق إنشاء كائنات
Formatterمناسبة واستدعاءformat()لكل نوع:- قم بتنسيق العدد الصحيح باستخدام
Formatter<int> - قم بتنسيق العدد العشري باستخدام
Formatter<double> - قم بتنسيق القيمة المنطقية باستخدام
Formatter<bool>(قم بتحويل النص المدخل إلى bool أولاً) - قم بتنسيق النص باستخدام
Formatter<const char*>
ثم استعرض تخصيص قالب الدالة:
- استدعِ
formatPairمع عددين صحيحين: 10 و 20 - استدعِ
formatPairمع نصين من نوع C-strings:"hello"و"world"
على سبيل المثال، مع المدخلات 42، 3.14، true، و Hello:
Value: 42
Value: 3.14
Boolean: true
String: "Hello"
Pair: [10, 20]
Pair: ["hello", "world"]لاحظ كيف يتعامل القالب العام مع الأعداد الصحيحة والعشرية بشكل متطابق، بينما توفر الإصدارات المتخصصة لـ bool و const char* تنسيقاً مخصصاً. يقوم المترجم (compiler) تلقائياً باختيار المطابقة الأكثر تحديداً لكل نوع تستخدمه.
ورقة مرجعية
يسمح لك تخصيص القوالب (Template specialization) بتوفير تطبيقات مخصصة لأنواع معينة مع الاحتفاظ بنسخة عامة لجميع الأنواع الأخرى.
صيغة التخصيص الكامل (Full Specialization Syntax)
قم بالتصريح عن التخصيص باستخدام قائمة معلمات قالب فارغة template <> وحدد النوع الملموس:
// Primary template
template <typename T>
class Printer {
public:
void print(T value) {
std::cout << value << std::endl;
}
};
// Full specialization for const char*
template <>
class Printer<const char*> {
public:
void print(const char* value) {
std::cout << "String: " << value << std::endl;
}
};
تخصيص قوالب الدوال (Function Template Specialization)
يمكن أيضاً تخصيص قوالب الدوال:
template <typename T>
bool isEqual(T a, T b) {
return a == b;
}
template <>
bool isEqual<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) == 0;
}
اختيار المترجم (Compiler Selection)
يفضل المترجم دائماً المطابقة الأكثر تحديداً - إذا كان هناك تخصيص موجود للنوع الدقيق المستخدم، فسيتم اختياره بدلاً من القالب العام.
جرّب بنفسك
#include <iostream>
#include <string>
#include "Formatter.h"
using namespace std;
int main() {
// قراءة المدخلات
int intVal;
double doubleVal;
string boolStr;
string strVal;
cin >> intVal;
cin >> doubleVal;
cin >> boolStr;
cin >> strVal;
// تحويل string إلى bool
bool boolVal = (boolStr == "true");
// TODO: قم بإنشاء Formatter<int> وتنسيق العدد الصحيح
// TODO: قم بإنشاء Formatter<double> وتنسيق العدد العشري
// TODO: قم بإنشاء Formatter<bool> وتنسيق القيمة المنطقية
// TODO: قم بإنشاء Formatter<const char*> وتنسيق السلسلة النصية
// تلميح: استخدم strVal.c_str() للحصول على const char*
// TODO: استدعِ formatPair مع عددين صحيحين: 10 و 20
// TODO: استدعِ formatPair مع سلسلتين نصيتين من نوع C-strings: "hello" و "world"
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة