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

Πρότυπα συναρτήσεων & Αφαίρεση τύπου

Μάθετε πώς να γράφετε συναρτήσεις που λειτουργούν με οποιονδήποτε τύπο. Κατανοήστε την αφαίρεση ορισμάτων προτύπου, τις παραμέτρους που δεν είναι τύπου και γιατί τα πρότυπα ζουν σε κεφαλίδες.

Γιατί Πρότυπα Συναρτήσεων;

Χωρίς πρότυπα, η σύνταξη μιας συνάρτησης που λειτουργεί για πολλούς τύπους σημαίνει αντιγραφή λογικής. Θα γράφατεint max(int, int),double max(double, double),std::string max(std::string, std::string)— η ίδια λογική επαναλαμβάνεται για κάθε τύπο. Τα πρότυπα συναρτήσεων σας επιτρέπουν να γράψετε τον αλγόριθμο μία φορά και να αφήσετε τον μεταγλωττιστή να δημιουργήσει αυτόματα εκδόσεις για συγκεκριμένους τύπους.

Ένα πρότυπο συνάρτησης δεν είναι συνάρτηση — είναι ένα σχεδιάγραμμα από το οποίο ο μεταγλωττιστής δημιουργεί πραγματικές συναρτήσεις όταν τις χρησιμοποιείτε με συγκεκριμένους τύπους. Αυτό συμβαίνει εξ ολοκλήρου τη στιγμή της μεταγλώττισης, δημιουργώντας κώδικα πανομοιότυπο με αυτό που θα γράφατε με το χέρι — μηδενική επιβάρυνση.

Σύνταξη προτύπου

Εισάγεται ένα πρότυπο συνάρτησης με τη λέξη-κλειδί template που ακολουθείται από μια λίστα παραμέτρων σε γωνιακές αγκύλες. Μπορείτε να χρησιμοποιήσετε το typename ή το class εναλλακτικά για τις παραμέτρους τύπου — σημαίνουν ακριβώς το ίδιο πράγμα. Η σύγχρονη σύμβαση προτιμά τοtypename.

function_template.cpp
#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/Vatile ανώτατου επιπέδου αφαιρείται από τις παραμέτρους τιμών αλλά διατηρείται για παραμέτρους αναφοράς/δείκτη.
5. Σύμπτυξη αναφοράς — εάν τοTσυνάγεται ως τύπος αναφοράς (στις αναφορές προώθησης), ισχύουν κανόνες σύμπτυξης αναφοράς: ΤοT& &γίνεταιT&, τοT& &&γίνεταιT&, τοT&& &γίνεταιT&, τοT&& &&γίνεται XCODE.

Όταν η αφαίρεση αποτυγχάνει ή είναι διφορούμενη, πρέπει να παρέχετε ορίσματα προτύπου ρητά:maximum(3, 7.0).

Παράμετροι προτύπου μη τύπου

Οι παράμετροι προτύπου δεν περιορίζονται σε τύπους. Οι παράμετροι προτύπου που δεν είναι τύπου (NTTP) δέχονται τιμές χρόνου μεταγλώττισης, όπως ακέραιους αριθμούς, δείκτες ή (από το C++20) τύπους κινητής υποδιαστολής και κλάσεων. Είναι ισχυρά για την ενσωμάτωση σταθερών χρόνου μεταγλώττισης στο σύστημα τύπου.

non_type_params.cpp
#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και προσπαθήσετε να τον χρησιμοποιήσετε από άλλη μονάδα μετάφρασης, θα λάβετε ένα σφάλμα σύνδεσης (απροσδιόριστη αναφορά). Η τυπική λύση είναι να τοποθετήσετε ολόκληρο τον ορισμό του προτύπου στο αρχείο κεφαλίδας. Εάν οι χρόνοι μεταγλώττισης γίνουν πρόβλημα, μπορείτε να χρησιμοποιήσετε τη explicit instantiation: βάλτε τον ορισμό σε ένα αρχείο.cppκαι προσθέστε τοtemplate int maximum(int, int);για να δημιουργήσετε ρητά τις εξειδικεύσεις που χρειάζεστε.

Ρητή εγκατάσταση

Μπορείτε να ελέγξετε ρητά πού και πότε δημιουργούνται τα πρότυπα. Αυτό μπορεί να μειώσει τους χρόνους μεταγλώττισης σε μεγάλα έργα, αποτρέποντας περιττές παρουσίες σε μεταφραστικές μονάδες.

explicit_instantiation.h
// ---- 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 XP)

1. Τι συμβαίνει όταν καλείτε το`maximum(3, 7.0)`με ένα πρότυπο`template<typename T> T maximum(T a, T b)`;

2. Why must template definitions typically be placed in header files?

3. Ποιος είναι ο σκοπός του`extern template int maximum<int>(int, int);`;