انتقل إلى المحتوى

قوالب الفصل وCTAD

إنشاء هياكل بيانات عامة باستخدام قوالب الفصل الدراسي. فهم تعريفات الأعضاء والوسائط الافتراضية وكيف يتيح لك CTAD الخاص بـ C++17 حذف وسائط القالب.

قوالب الصف

يحدد قالب الفئة مجموعة من الفئات ذات معلمات بواسطة نوع واحد أو أكثر (أو قيم غير نوع). تم بناء المكتبة القياسية على قوالب الفئة:std::vector،std::map،std::optional،std::shared_ptr. عندما تكتبstd::vector، يقوم المترجم بإنشاء مثيل للفئة بأكملها باستخدامT = int، مما يؤدي إلى إنشاء نوع ملموس.

تتبع قوالب الفئة نفس القواعد التي تتبعها قوالب الوظائف: يجب أن يتم تعريفها في الرؤوس، وكل مجموعة فريدة من وسيطات القالب تنتج نوعًا مميزًا.StackوStackهما أنواع غير مرتبطة تمامًا — ولا يتشاركان في أي علاقة ميراث.

كومة عامة

هنا مكدس عام بسيط. لاحظ كيف تتطلب وظائف الأعضاء المحددة خارج نص الفصل تكرار قائمة معلمات القالب.

stack.hpp
#include <vector>
#include <stdexcept>
#include <iostream>

template <typename T>
class Stack {
private:
    std::vector<T> elements_;

public:
    void push(const T& value);
    void pop();
    const T& top() const;
    bool empty() const { return elements_.empty(); }
    std::size_t size() const { return elements_.size(); }
};

// Member definitions outside the class require the template header
template <typename T>
void Stack<T>::push(const T& value) {
    elements_.push_back(value);
}

template <typename T>
void Stack<T>::pop() {
    if (elements_.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    elements_.pop_back();
}

template <typename T>
const T& Stack<T>::top() const {
    if (elements_.empty()) {
        throw std::out_of_range("Stack<>::top(): empty stack");
    }
    return elements_.back();
}

int main() {
    Stack<int> intStack;
    intStack.push(42);
    intStack.push(17);
    std::cout << intStack.top() << '\n'; // 17
    intStack.pop();
    std::cout << intStack.top() << '\n'; // 42
}

معلمات وافتراضيات متعددة

يمكن أن تحتوي قوالب الفئة على معلمات قالب متعددة و وسائط قالب افتراضية، تمامًا كما يمكن أن تحتوي معلمات الوظائف على قيم افتراضية. يتم حل الوسائط الافتراضية من اليسار إلى اليمين.

stack_defaults.cpp
#include <vector>
#include <deque>
#include <iostream>

// Container defaults to std::deque<T> — matches std::stack's design
template <typename T, typename Container = std::deque<T>>
class Stack {
private:
    Container elements_;

public:
    void push(const T& value) { elements_.push_back(value); }
    void pop()                { elements_.pop_back(); }
    const T& top() const      { return elements_.back(); }
    bool empty() const        { return elements_.empty(); }
};

int main() {
    Stack<int> s1;                         // uses deque<int>
    Stack<int, std::vector<int>> s2;       // uses vector<int>

    s1.push(10);
    s2.push(20);
    std::cout << s1.top() << '\n'; // 10
    std::cout << s2.top() << '\n'; // 20
}

خصم وسيطة قالب الفصل (CTAD)

قبل C++17، كان عليك دائمًا تحديد وسيطات القالب عند إنشاء مثيلات قالب الفئة:std::pair p(1, 3.14);. CTAD (خصم وسيطة قالب الفئة)، الذي تم تقديمه في C++17، يتيح للمترجم استنتاج وسيطات قالب الفئة من وسيطات المُنشئ، تمامًا مثل خصم وسيطة قالب الوظيفة.

باستخدام CTAD:std::pair p(1, 3.14);- يستنتج المترجمstd::pair. يعمل هذا معstd::vector، وstd::tuple، وstd::optional، وstd::array(C++20)، وقوالب الفصل الدراسي الخاصة بك.

يستخدم CTAD أدلة الاستنباط — إما ضمنية (يتم إنشاؤها من المنشئين) أو صريحة (محددة من قبل المستخدم). يحاول المترجم كل دليل ويختار أفضل تطابق.

CTAD وأدلة الخصم

تخبر أدلة الاستنباط المحددة من قبل المستخدم المترجم بكيفية تعيين وسيطات المنشئ إلى وسيطات القالب. وهي مفيدة بشكل خاص عندما لا يتوافق نوع وسيطة المُنشئ مباشرةً مع معلمة القالب.

ctad_example.cpp
#include <iostream>
#include <cstddef>

template <typename T, std::size_t N>
struct Array {
    T data[N];
};

// Without a deduction guide, CTAD cannot deduce T and N.
// This explicit deduction guide maps brace-init with N elements
// of the same type T to Array<T, N>.
template <typename First, typename... Rest>
Array(First, Rest...) -> Array<First, 1 + sizeof...(Rest)>;

// Standard library example: std::vector CTAD from iterators
#include <vector>
#include <list>

int main() {
    // CTAD with our custom deduction guide
    Array arr{1, 2, 3, 4, 5};  // deduces Array<int, 5>
    std::cout << arr.data[2] << '\n'; // 3

    // Standard library CTAD examples (C++17)
    std::pair p(42, 3.14);       // pair<int, double>
    std::vector v{1, 2, 3};     // vector<int>
    std::tuple t(1, 2.0, 'a');  // tuple<int, double, char>

    // CTAD from iterator range
    std::list<int> lst{10, 20, 30};
    std::vector v2(lst.begin(), lst.end()); // vector<int>
}

قوالب الأعضاء

يمكن أن تكون وظائف الأعضاء في قالب الفصل عبارة عن قوالب، مما يضيف معلمات قالب إضافية. وهذا ما يسمى قالب الأعضاء. إحدى حالات الاستخدام الشائعة هي تمكين التعيين أو البناء من إنشاء مثيل ذي صلة ولكن مختلف.

على سبيل المثال، قد ترغب في تعيينStackمنStack— ولكن نظرًا لأن هذه الأنواع غير مرتبطة، فلن يعمل تعيين النسخة الافتراضية. عامل تعيين قالب العضو يحل هذا:

template <typename T>
class Stack {
public:
    template <typename U>
    Stack& operator=(const Stack<U>& other);
};

Inside this member template, U may differ from T. The body can copy elements from other as long as U is convertible to T. This pattern is used extensively in the standard library — std::shared_ptr converts to std::shared_ptr عبر منشئ قالب العضو.

خطأ شائع

عندما تقوم بتخصيص قالب فصل دراسي، يكون التخصص فئة جديدة تمامًا — ولا يرث أي أعضاء من القالب الأساسي. يجب عليك إعادة تعريف كل وظيفة عضو وعضو بيانات. يؤدي نسيان إعادة تعريف عضو في التخصص إلى حدوث خطأ في الترجمة عند استخدام هذا العضو. كما أن التخصص الجزئي متاح فقط لقوالب الفصل الدراسي، وليس لقوالب الوظائف. نحن نغطي التخصص بالتفصيل في الفصل التالي.

النقاط الرئيسية
  • تحدد قوالب الفصل عائلات الفئات - تنتج كل مجموعة وسائط فريدة نوعًا مميزًا
  • يجب أن تكرر وظائف الأعضاء المحددة خارج الفصل رأسtemplate وتستخدمClassName::
  • تعمل وسيطات القالب الافتراضية من اليسار إلى اليمين، تمامًا مثل وسيطات الوظائف الافتراضية
  • يتيح CTAD (C++17) للمترجم استنتاج وسيطات قالب الفئة من وسيطات المُنشئ
  • تقوم أدلة الاستقطاع الصريح بتعيين أنماط وسيطة المُنشئ إلى وسيطات القالب
  • تسمح قوالب الأعضاء بالعمليات عبر عمليات إنشاء مختلفة لنفس قالب الفئة

اختبار — اختبر معرفتك

(15 نقطة خبرة)

1. بالنظر إلى`template <typename T, typename Container = std::deque<T>> class Stack`، ما هو نوع`Stack<int>`؟

2. ما هو CTAD (C++17)؟

3. عندما تقوم بتخصيص قالب فصل دراسي، ماذا يحدث لأعضاء القالب الأساسي؟