0Pricing
C++ Academy · درس

أسلوب CRTP

نمط القالب المتكرر فضوليًا

أسلوب CRTP درس مجاني في C++ Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C++ Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C++ Academy 4 دروس في المجموع.

ما هو CRTP؟

إن Curiously Recurring Template Pattern (CRTP) أسلوب اصطلاحي في C++، ترث فيه فئة من قالب أساس مُنشأ باستخدام Derived نفسها.

  • الشكل: class Derived : public Base<Derived>
  • يتعرف الأساس على نوعه المشتق في وقت الترجمة
template <typename T>
class Base {};

class Derived : public Base<Derived> {};

الآلية الأساسية

داخل الأساس، يمكنك استخدام static_cast لتحويل مؤشر this إلى النوع المشتق. وبما أن النوع المشتق معامل قالب، فإن هذا التحويل يتم بالكامل في وقت الترجمة ومن دون تكلفة وقت تشغيل.

#include <iostream>

template <typename T>
class Base {
public:
    void interface() {
        static_cast<T*>(this)->implementation();
    }
};

class Derived : public Base<Derived> {
public:
    void implementation() { std::cout << "Derived impl\n"; }
};

int main() {
    Derived d;
    d.interface();
    return 0;
}

لماذا «متكرر على نحو غريب»؟

يأتي الاسم من المفارقة الظاهرة: إذ تشير الفئة الأساسية إلى الفئة المشتقة التي لم تُعرّف بالكامل بعد عند كتابة قالب الأساس.

  • يعمل ذلك لأن القوالب لا تُنشأ إلا عند استخدامها
  • وبحلول وقت الإنشاء، يكون Derived نوعًا مكتملًا

أول استخدام فعلي

يتيح CRTP للأساس توفير وظائف قابلة لإعادة الاستخدام تستدعي الفئة المشتقة. هنا يوفر الأساس دالة print() تفوّض التنفيذ إلى name() في الفئة المشتقة.

#include <iostream>
#include <string>

template <typename T>
struct Named {
    void print() {
        std::cout << static_cast<T*>(this)->name() << "\n";
    }
};

struct Cat : Named<Cat> {
    std::string name() { return "Cat"; }
};

int main() {
    Cat c;
    c.print();
    return 0;
}

لا توجد جداول افتراضية

على خلاف تعدد الأشكال في وقت التشغيل، لا يحتاج CRTP إلى الكلمة المفتاحية virtual ولا إلى مؤشر vtable في كل كائن. ويُحسم التوزيع بالكامل في وقت الترجمة.

  • كائنات أصغر حجمًا، لعدم وجود vptr مخفي
  • يمكن تضمين الاستدعاءات

عدّ النسخ

من التطبيقات التقليدية لـ CRTP عدّ الكائنات. إذ تحصل كل فئة مشتقة تلقائيًا على عدّاد ثابت خاص بها.

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
    ~Counter() { --count; }
};

struct Widget : Counter<Widget> {};

int main() {
    Widget a, b;
    std::cout << Widget::count << "\n";
    return 0;
}

تحصل كل فئة مشتقة على حالتها الخاصة

نظرًا إلى أن Counter<Widget> وCounter<Gadget> إنشاآن مختلفان للقالب، فلكل منهما count ثابت مستقل. ويستحيل تحقيق هذا الفصل باستخدام أساس واحد غير قالب.

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
};

struct A : Counter<A> {};
struct B : Counter<B> {};

int main() {
    A a1, a2;
    B b1;
    std::cout << A::count << " " << B::count << "\n";
    return 0;
}

يكون static_cast آمنًا هنا

يكون التحويل التنازلي داخل الأساس آمنًا فقط إذا كان كل كائن من النوع Base<T> هو بالفعل من النوع T. ويضمن CRTP ذلك بحكم بنائه، لأن T هي الفئة التي ترث.

  • لا تنشئ Base<Wrong> لفئة غير مرتبطة

عوامل المقارنة عبر CRTP

يمكنك إنشاء مجموعة كاملة من عوامل المقارنة انطلاقًا من operator< واحد، وذلك بالاشتقاق من مساعد للمقارنة.

#include <iostream>

template <typename T>
struct Comparable {
    friend bool operator>(const T& a, const T& b) { return b < a; }
    friend bool operator==(const T& a, const T& b) { return !(a < b) && !(b < a); }
};

struct Num : Comparable<Num> {
    int v;
    Num(int x) : v(x) {}
    friend bool operator<(const Num& a, const Num& b) { return a.v < b.v; }
};

int main() {
    std::cout << (Num(3) > Num(2)) << "\n";
    return 0;
}

حماية مُنشئ الأساس

لمنع إساءة الاستخدام غير المقصودة للأساس، امنحه مُنشئًا من نوع protected بحيث لا تتمكن من إنشائه إلا الفئات المشتقة.

#include <iostream>

template <typename T>
class Base {
protected:
    Base() = default;
public:
    void run() { static_cast<T*>(this)->step(); }
};

class Job : public Base<Job> {
public:
    void step() { std::cout << "step\n"; }
};

int main() {
    Job j;
    j.run();
    return 0;
}

CRTP في المكتبات القياسية

يظهر CRTP في جميع أنحاء مكتبات C++ الفعلية.

  • يستخدمه std::enable_shared_from_this<T>
  • تعتمد عليه العديد من مكتبات الرياضيات القائمة على قوالب التعابير، مثل Eigen
  • تستخدم النطاقات ومساعدات المكررات أنماطًا مشابهة

يساعدك التعرف على الشكل X : Base<X> في قراءة الشيفرة المتقدمة.

تحقق سريع

اختبر مدى فهمك لشكل CRTP.

مراجعة

لقد تعلمت أسلوب CRTP الاصطلاحي.

  • يمنح Derived : Base<Derived> الأساسَ نوعه المحدد
  • يتيح static_cast<T*>(this) التوزيع في وقت الترجمة
  • لا توجد vtable، وتكون الكائنات أصغر حجمًا، ويمكن تضمين الاستدعاءات
  • يُستخدم للعدادات وخلطات المقارنة ومساعدات المكتبات

الأسئلة الشائعة

هل درس «أسلوب CRTP» مجاني؟

نعم — نص درس «أسلوب CRTP» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C++ Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C++ Academy 4 دروس في المجموع.

ماذا ستتعلم في «أسلوب CRTP»؟

نمط القالب المتكرر فضوليًا تتمرن على C++ Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C++ Academy؟

لا تُشترط خبرة سابقة. C++ Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «أسلوب CRTP»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C++ Academy هذا؟

نعم. كل درس في C++ Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أسلوب CRTP
  2. تعدد الأشكال الساكن
  3. Mixins باستخدام CRTP
  4. متى تستخدم CRTP
← العودة إلى C++ Academy