Μετάβαση στο περιεχόμενο

Πρότυπα τάξης & CTAD

Δημιουργήστε γενικές δομές δεδομένων με πρότυπα κλάσεων. Κατανοήστε τους ορισμούς των μελών, τα προεπιλεγμένα ορίσματα και πώς το CTAD του C++17 σάς επιτρέπει να παραλείπετε ορίσματα προτύπου.

Πρότυπα τάξης

A class template defines a family of classes parameterized by one or more types (or non-type values). The standard library is built on class templates:std::vector,std::map,std::optional,std::shared_ptr. When you writestd::vector, the compiler instantiates the entire class withT = int, generating a concrete type.

Class templates follow the same rules as function templates: they must be defined in headers, and each unique set of template arguments produces a distinct type.StackandStackare completely unrelated types — they share no inheritance relationship.

Μια γενική στοίβα

Here is a simple generic stack. Note how member functions defined outside the class body require repeating the template parameter list.

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
}

Multiple Parameters & Defaults

Class templates can have multiple template parameters and default template arguments, just like function parameters can have default values. Default arguments are resolved left to right.

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
}

Class Template Argument Deduction (CTAD)

Πριν από το C++17, έπρεπε πάντα να προσδιορίζετε ορίσματα προτύπου κατά τη δημιουργία παρουσιών προτύπων κλάσης:std::pair p(1, 3.14);. Το CTAD (Class Template Argument Deduction), που εισήχθη στο C++17, επιτρέπει στον μεταγλωττιστή να συναγάγει ορίσματα προτύπου κλάσης από ορίσματα κατασκευαστή, ακριβώς όπως η αφαίρεση ορίσματος προτύπου συνάρτησης.

Με το CTAD:std::pair p(1, 3.14);— ο μεταγλωττιστής συμπεραίνει τοstd::pair. Αυτό λειτουργεί για ταstd::vector,std::tuple,std::optional,std::array(C++20) και τα δικά σας πρότυπα τάξης.

Το CTAD χρησιμοποιεί οδηγούς αφαίρεσης — είτε σιωπηρούς (που δημιουργούνται από κατασκευαστές) είτε ρητά (καθορίζονται από τον χρήστη). Ο μεταγλωττιστής δοκιμάζει κάθε οδηγό και επιλέγει την καλύτερη αντιστοιχία.

Οδηγοί CTAD & Deduction

Οι οδηγοί έκπτωσης που ορίζονται από το χρήστη λένε στον μεταγλωττιστή πώς να αντιστοιχίσει ορίσματα κατασκευαστή σε ορίσματα προτύπου. Είναι ιδιαίτερα χρήσιμα όταν ο τύπος ορίσματος του κατασκευαστή δεν αντιστοιχεί άμεσα στην παράμετρο προτύπου.

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 άλλο 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 XP)

1. Δεδομένου του`template <typename T, typename Container = std::deque<T>> class Stack`, ποιος είναι ο τύπος του`Stack<int>`;

2. Τι είναι το CTAD (C++17);

3. Όταν εξειδικεύετε ένα πρότυπο τάξης, τι συμβαίνει με τα μέλη του πρωτεύοντος προτύπου;