Șabloane C++

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită la 30 martie 2016; verificările necesită 29 de modificări .

Templates ( eng.  template ) este un instrument de limbaj C++ conceput pentru codificarea algoritmilor generalizați , fără a fi legat de unii parametri (de exemplu, tipuri de date , dimensiuni de buffer, valori implicite).

În C++ este posibil să se creeze șabloane de funcție și de clasă .

Șabloanele vă permit să creați clase și funcții parametrizate. Parametrul poate fi orice tip sau o valoare a unuia dintre tipurile permise (întreg, enumerare, pointer către orice obiect cu un nume accesibil la nivel global, referință). De exemplu, avem nevoie de o clasă:

clasa SomeClass { int SomeValue ; int SomeArray [ 20 ]; ... };

Pentru un scop anume, putem folosi această clasă. Dar, brusc, obiectivul s-a schimbat puțin, și este nevoie de o altă clasă. Acum avem nevoie de 30 de elemente matrice și un tip de element SomeArrayreal . Apoi ne putem abstra de la tipurile concrete și putem folosi șabloane cu parametri. Sintaxă: la început, înainte de a declara clasa, declarăm șablonul, adică precizăm parametrii între paranteze unghiulare. În exemplul nostru: SomeValueSomeArraytemplate

template < int ArrayLength , typename SomeValueType > class SomeClass { SomeValueType SomeValue ; SomeValueType SomeArray [ ArrayLength ]; ... };

Apoi, pentru primul caz (cu întreg SomeValue și SomeArray de 20 de elemente) scriem:

SomeClass < 20 , int > SomeVariable ;

pentru al doilea:

SomeClass < 30 , double > SomeVariable2 ;

Deși șabloanele oferă o prescurtare pentru o bucată de cod, utilizarea lor nu scurtează de fapt codul executabil, deoarece compilatorul creează o instanță separată a unei funcții sau a unei clase pentru fiecare set de opțiuni. Ca urmare, abilitatea de a partaja cod compilat în bibliotecile partajate dispare.

Șabloane de funcție

Descriere șablon Sintaxă

Un șablon de funcție începe cu cuvântul cheie templateurmat de o listă de parametri între paranteze unghiulare. Apoi vine declarația funcției:

template < typename T > void sort ( T array [], int size ); // prototip: șablonul de sortare este declarat, dar nu este definit template < typenameT > _ void sort ( T array [], int size ) // declarație și definiție { T t ; pentru ( int i = 0 ; i < dimensiune - 1 ; i ++ ) pentru ( int j = dimensiune - 1 ; j > i ; j -- ) dacă ( matrice [ j ] < matrice [ j -1 ]) { t = tablou [ j ]; matrice [ j ] = matrice [ j -1 ]; tablou [ j -1 ] = t ; } } template < int BufferSize > // parametru întreg char * read () { char * Buffer = new char [ BufferSize ]; /* citește datele */ returnare tampon ; }

Cuvântul cheie este typenamerelativ recent, astfel încât standardul [1] permite utilizarea classîn loc de typename:

șablon < classT > _

În loc de T , orice alt identificator este acceptabil.

Exemplu de utilizare

Cel mai simplu exemplu este determinarea minimului a două mărimi.

Dacă a este mai mic decât b atunci returnează a, în caz contrar returnează b

În absența șabloanelor, programatorul trebuie să scrie funcții separate pentru fiecare tip de date utilizat. Deși multe limbaje de programare definesc o funcție minimă încorporată pentru tipurile elementare (cum ar fi numere întregi și numere reale), o astfel de funcție poate fi necesară pentru complexe (de exemplu, „timp” sau „șir”) și foarte complexe („ jucător” într- un joc online ) obiecte .

Iată cum arată șablonul de funcție minimă:

template < typenameT > _ T min ( T a , T b ) { returnează a < b ? a : b ; }

Pentru a apela această funcție, puteți folosi pur și simplu numele ei:

min ( 1 , 2 ); min ( 'a' , 'b' ); min ( șir ( " abc " ), șir ( " cde " ) );

Apel de funcție șablon

În general, pentru a apela o funcție șablon, trebuie să furnizați valori pentru toți parametrii șablonului. Pentru a face acest lucru, după numele șablonului, este indicată o listă de valori \u200b\u200bis între paranteze unghiulare:

int i [] = { 5 , 4 , 3 , 2 , 1 }; sortare < int > ( i , 5 ); char c [] = "bvgda" ; sort < char > ( c , strlen ( c ) ); sortare < int > ( c , 5 ); // eroare: sort<int> are un parametru int[], nu un char[] char * ReadString = citire < 20 > (); șterge [] ReadString ; ReadString = citire < 30 > ();

Pentru fiecare set de opțiuni, compilatorul generează o nouă instanță a funcției. Procesul de creare a unei noi instanțe se numește instanțiere șablon .

În exemplul de mai sus, compilatorul a creat două specializări de șablon de funcție sort(pentru tipurile charși int) și două specializări de șablon read(pentru valorile BufferSize20 și 30). Cel din urmă este cel mai probabil risipitor, deoarece pentru fiecare valoare posibilă a parametrului, compilatorul va crea tot mai multe instanțe noi de funcții care vor diferi doar cu o singură constantă.

Derivarea valorilor parametrilor

În unele cazuri, compilatorul poate deduce (determina logic) valoarea unui parametru șablon de funcție dintr-un argument de funcție. De exemplu, la apelarea funcției descrise mai sus, nu este sortnecesar să specificați parametrul șablon (dacă se potrivește cu tipul elementelor argumentului matricei):

int i [ 5 ] = { 5 , 4 , 3 , 2 , 1 }; sortare ( i , 5 ); // apel sort<int> char c [] = "bvgda" ; sort ( c , strlen ( c ) ); // apel sort<char>

Îndepărtarea este posibilă și în cazuri mai complexe .

În cazul utilizării șabloanelor de clasă cu parametri întregi, este de asemenea posibil să se deducă acești parametri. De exemplu:

template < int size > clasa IntegerArray { int Array [ dimensiune ]; /* ... */ }; template < int size > // Template prototype void PrintArray ( IntegerArray < size > array ) { /* ... */ } // Template call // Folosind obiectul șablon IntegerArray < 20 > ia ; PrintArray ( ia );

Regulile de inferență sunt introduse în limbaj pentru a facilita utilizarea unui șablon și pentru a evita posibile erori, cum ar fi încercarea sort< int >de a sorta o serie de caractere.

Dacă un parametru șablon poate fi dedus din mai multe argumente, atunci rezultatul inferenței trebuie să fie exact același pentru toate aceste argumente. De exemplu, următoarele apeluri sunt eronate:

min ( 0 , 'a' ); min ( 7 , 7,0 );

Greșeli în șabloane

Erorile asociate cu utilizarea unor parametri specifici de șablon nu pot fi detectate înainte de a fi utilizat șablonul. De exemplu, șablonul minîn sine nu conține erori, dar folosirea lui cu tipuri pentru care operația '<'nu este definită va duce la o eroare:

structura A { int a ; }; A obj1 , obj2 ; min ( obj1 , obj2 );

Dacă introduceți operația '<'înainte de prima utilizare a șablonului, atunci eroarea va fi eliminată. Iată cum se manifestă flexibilitatea șabloanelor în C++ :

friend inline bool operator < ( const A & a1 , const A & a2 ) { return a1 . a < a2 . a ; } min ( obj1 , obj2 );

Șabloane de clasă

Într-o clasă care implementează o listă legată de numere întregi, algoritmii pentru adăugarea unui nou element la listă și căutarea elementului dorit nu depind de faptul că elementele listei sunt numere întregi. Aceiași algoritmi s-ar aplica unei liste de personaje, șiruri, date, clase de jucători și așa mai departe.

șablon < classT > _ Lista clasei { /* ... */ public : void Add ( const T & Element ); bool Find ( const T & Element ); /* ... */ };

Folosind șabloane

Pentru a utiliza un șablon de clasă, trebuie să specificați parametrii acestuia:

List < int > li ; Listă < șir > ls ; li . adăugați ( 17 ); ls . Adaugă ( "Bună ziua!" );

Detalii tehnice

Opțiuni șablon

Parametrii șablonului pot fi: parametri de tip, parametri de tip obișnuit, parametri de șablon.

Puteți specifica valori implicite pentru parametrii de orice tip.

template < clasa T1 , // tip parametru nume tip T2 , // tip parametru int I , // parametru obișnuit T1 DefaultValue , // tip obișnuit parametru șablon < clasă > clasa T3 , // șablon parametru clasa Caracter = caracter // implicit parametru > Parametrii șablonului

Dacă este necesar să folosiți același șablon într-un șablon de clasă sau funcție, dar cu parametri diferiți, atunci sunt utilizați parametrii șablon. De exemplu:

template < class Type , template < class > class Container > Referințe încrucișate de clasă { Container < Type > mems ; Container < Tip * > refs ; /* ... */ }; CrossReferences < Data , vector > cr1 ; CrossReferences < șir , set > cr2 ;

Șabloanele de funcție nu pot fi utilizate ca parametri de șablon.

Reguli pentru deducerea argumentelor șablonului funcției

Pentru parametrii care sunt tipuri (de exemplu, parametrul T al funcției de sortare), este posibilă inferența dacă argumentul funcției este de unul dintre următoarele tipuri:

Tipul argumentului Descriere
T
const T
volatile T
Tipul în sine T, eventual cu modificatori constsau volatile. șablon < classT > _ T ReturnMe ( const T arg ) { return arg ; } ReturnMe ( 7 ); ReturnMe ( 'a' );
T*
T&
T[A]
A este o constantă
Un indicator, referință sau o matrice de elemente de tip T.

Un exemplu este șablonul funcției de sortare discutat mai sus.

Templ<T>
Templ - nume șablon de clasă
Ca argument, funcția necesită o specializare specifică a unui șablon. #include <vector> șablon < classT > _ void sort ( vector < T > matrice ) { /* sort */ } vector < int > i ; vector < char > c ; sortare ( i ); sortare ( c );
T (*) (args)
args - unele argumente
Indicator către o funcție care returnează tipul T. șablon < classT > _ T * CreateArray ( T ( * GetValue )(), const int size ) { T * Array = nou T [ dimensiune ]; pentru ( int i = 0 ; i < dimensiune ; i ++ ) Matrice [ i ] = GetValue (); return Array ; } int GetZero () { return 0 ; } char InputChar () { char c ; cin >> c ; returnează c ; } int * ArrayOfZeros = CreateArray ( GetZero , 20 ); char * String = CreateArray ( InputChar , 40 );
type T::*
T Class::*
tip - un tip
Clasă - o clasă
Un pointer către un membru al clasei T de tip arbitrar.
Indicator către un membru de tip T al unei clase arbitrare. clasa MyClass { public : int a ; }; șablon < classT > _ T & IncrementIntegerElement ( int T ::* Element , T & Object ) { obiect . * Element += 1 ; return Object ; } șablon < classT > _ T IncrementMyClassElement ( T MyClass ::* Element , MyClass & Object ) { obiect . * Element += 1 ; return Object . * Element ; } MyClass Obj ; int n ; n = ( IncrementIntegerElement ( & MyClass :: a , Obj ) ). a ; n = IncrementMyClassElement ( & MyClass :: a , Obj );
type (T::*) (args)
T (Class::*) (args)
tip - un tip
Clasă - unele
argumente de clasă - unele argumente
Pointer către o funcție membru din clasa T de tip arbitrar.
Pointer către o funcție membru de tip T a unei clase arbitrare. clasa MyClass { public : int a ; int IncrementA (); }; int MyClass::IncrementA () { return ++ a ; } șablon < classT > _ T & CallIntFunction ( int ( T ::* Funcție )(), T & Object ) { ( Obiect . * Funcție )(); return Object ; } șablon < classT > _ T CallMyClassFunction ( T ( MyClass ::* Function )(), MyClass & Object ) { return ( Obiect . * Funcție )(); } MyClass Obj ; int n ; n = ( CallIntFunction ( & MyClass :: IncrementA , Obj ) ). a ; n = CallMyClassFunction ( & MyClass :: IncrementA , Obj );

Membrii claselor șablon

Membrii unui șablon de clasă sunt șabloane și cu aceeași parametrizare ca șablonul de clasă. În special, aceasta înseamnă că definiția funcțiilor membre ar trebui să înceapă cu antetul șablonului:

șablon < classT > _ clasa A { void f ( T date ); void g ( gol ); public : A (); }; șablon < classT > _ void A < T >:: f ( T date ); șablon < classT > _ gol A < T >:: g ( gol );

În domeniul șablonului, specificatorul nu trebuie repetat. Aceasta înseamnă că, de exemplu A<T>::A() , este un constructor , deși puteți scrie și A<T>::A<T>().

Tipuri ca membri ai claselor

Dacă parametrul șablon este o clasă care are un membru care este de tip de date , atunci cuvântul cheie trebuie utilizat pentru a utiliza acel membru typename. De exemplu:

clasa Container { public : int matrice [ 15 ]; typedef int * iterator ; /* ... */ iterator begin () { return array ; } }; șablon < clasa C > void f ( C și vector ) { C :: iterator i = vector . începe (); // nume de tip eroare C :: iterator i = vector . începe (); } Șabloane ca membri ai claselor

Există și probleme cu membrii șablonului. Dacă un șablon (ConvertTo()), care este membru al unei clase (A), care la rândul său este un parametru șablon (f), este utilizat în acest șablon (f) și nu permite deducerea parametrilor, atunci calificativul trebuie folosit template:

clasa A { /* ... */ public : template < clasa T > T & ConvertTo (); template < class T > void ConvertFrom ( const T & date ); }; șablon < classT > _ void f ( T Container ) { int i1 = Container . template ConvertTo < int > () + 1 ; recipient . ConvertFrom ( i1 ); // nu este nevoie de calificativ }

Critică și comparație cu alternative

Metaprogramarea șabloanelor în C++ suferă de multe limitări, inclusiv probleme de portabilitate, lipsă de depanare sau suport I/O în timpul instanțierii șablonului, timpi lungi de compilare, lizibilitate slabă a codului, diagnosticare slabă a erorilor și mesaje de eroare obscure [2] . Subsistemul șablon C++ este definit ca un limbaj de programare funcțional pur și complet Turing, dar programatorii în stil funcțional văd acest lucru ca pe o provocare și sunt reticenți în a recunoaște C++ ca un limbaj de succes [3] .

Multe limbaje ( Java 5, Ada , Delphi 2009) implementează suport de programare generică într -un mod mai simplu, unele chiar la nivel de sistem de tip (vezi Eiffel și polimorfismul parametric în familia de limbaje ML ); astfel de limbaje nu au nevoie de mecanisme similare șabloanelor C++.

Facilitățile de substituție macro ale lui C, deși nu sunt complete Turing, sunt suficiente pentru programarea la nivel scăzut în programarea generativă , iar capacitățile lor au fost extinse semnificativ în C99 .

Limbajul D are șabloane care sunt mai puternice decât C++. [4] .

Vezi și

Note

  1. Standardul C++ „Standard pentru limbajul de programare C++”: ISO/IEC 14882 1998 .
  2. K. Czarnecki, J. O'Donnell, J. Striegnitz, W. Taha. Implementarea DSL în metaocaml, template haskell și C++ . — Universitatea din Waterloo, Universitatea din Glasgow, Centrul de Cercetare Julich, Universitatea Rice, 2004. .
    Citat: Metaprogramarea șabloanelor C++ suferă de o serie de limitări, inclusiv probleme de portabilitate din cauza limitărilor compilatorului (deși acest lucru s-a îmbunătățit semnificativ în ultimii câțiva ani), lipsa suportului de depanare sau IO în timpul instanțierii șablonului, timpi lungi de compilare, erori lungi și de neînțeles , lizibilitate slabă a codului și raportare slabă a erorilor.
  3. Sheard T., Jones SP Template Metaprogramare pentru Haskell  // Haskell Workshop. - Pittsburgh: ACM 1-58113-415-0/01/0009, 2002. .
    Un citat din lucrarea provocatoare a lui Robinson identifică șabloanele C++ ca un succes major, deși accidental, al designului limbajului C++. În ciuda naturii extrem de baroc a meta-programarii șabloane, șabloanele sunt folosite în moduri fascinante, care se extind dincolo de cele mai sălbatice vise ale designerilor de limbi. Poate în mod surprinzător, având în vedere faptul că șabloanele sunt programe funcționale, programatorii funcționali au întârziat să valorifice succesul C++
  4. ↑ Digital Mars : D Programming Language 2.0  

Literatură

  • David Vandevoerd, Nicholas M. Josattis. Șabloane C ++: Ghidul complet = Șabloane C++: Ghidul complet. - M . : „Williams” , 2003. - S.  544 . — ISBN 0-201-73484-2 .
  • Podbelsky V. V. 6.9. Șabloane de funcții //Capitolul 6. Funcții, pointeri, referințe // Limbajul C++ / recenzie. Dadaev Yu. G. - 4. - M .: Finanțe și statistică , 2003. - S. 230-236. — 560 p. - ISBN 5-279-02204-7 , UDC 004.438Si (075.8) LBC 32.973.26-018 1ya173.

Link -uri