Templates ( eng. template ) är ett C++- språkverktyg utformat för att koda generaliserade algoritmer , utan att vara bundna till vissa parametrar (till exempel datatyper , buffertstorlekar, standardvärden).
I C++ är det möjligt att skapa funktions- och klassmallar .
Mallar låter dig skapa parametriserade klasser och funktioner. Parametern kan vara vilken typ som helst eller ett värde av en av de tillåtna typerna (heltal, enum, pekare till vilket objekt som helst med ett globalt tillgängligt namn, referens). Till exempel behöver vi någon klass:
klass SomeClass { int SomeValue ; int SomeArray [ 20 ]; ... };För ett specifikt syfte kan vi använda den här klassen. Men, plötsligt har målet ändrats lite, och en annan klass behövs. Nu behöver vi 30 arrayelement SomeArrayoch en riktig SomeValueelementtyp SomeArray. Då kan vi abstrahera bort från konkreta typer och använda mallar med parametrar. Syntax: i början, innan vi deklarerar klassen, deklarerar vi mallen, det vill templatesäga vi anger parametrarna inom vinkelparenteser. I vårt exempel:
mall < int ArrayLength , typnamn SomeValueType > class SomeClass { SomeValueType SomeValue ; SomeValueType SomeArray [ ArrayLength ]; ... };Sedan för det första fallet (med heltal SomeValue och SomeArray med 20 element) skriver vi:
SomeClass < 20 , int > SomeVariable ;för den andra:
SomeClass < 30 , double > SomeVariable2 ;Även om mallar tillhandahåller en förkortning för en bit kod, förkortar användningen av dem faktiskt inte den körbara koden, eftersom kompilatorn skapar en separat instans av en funktion eller klass för varje uppsättning alternativ. Som ett resultat försvinner möjligheten att dela kompilerad kod inom delade bibliotek.
En funktionsmall börjar med nyckelordet templateföljt av en lista med parametrar inom vinkelparenteser. Sedan kommer funktionsdeklarationen:
mall < typnamn T > void sort ( T array [], int size ); // prototyp: sorteringsmall är deklarerad men inte definierad mall < typnamnT > _ void sort ( T array [], int size ) // deklaration och definition { Tt ; _ för ( int i = 0 ; i < storlek - 1 ; i ++ ) för ( int j = storlek - 1 ; j > i ; j -- ) if ( array [ j ] < array [ j -1 ]) { t = array [ j ]; array [ j ] = array [ j -1] ; array [ j - 1 ] = t ; } } mall < int BufferSize > // heltalsparameter char * read () { char * Buffer = ny char [ BufferSize ]; /* läs data */ returnera buffert ; }Nyckelordet är typenamerelativt nytt, så standarden [1] tillåter användning classistället för typename:
mall < classT > _Istället för T är vilken annan identifierare som helst acceptabel.
Det enklaste exemplet är bestämning av minsta två kvantiteter.
Om a är mindre än b, returnera a, annars returnera b
I avsaknad av mallar måste programmeraren skriva separata funktioner för varje datatyp som används. Även om många programmeringsspråk definierar en inbyggd minimifunktion för elementära typer (som heltal och reella tal), kan en sådan funktion behövas för komplexa (till exempel "tid" eller "sträng") och mycket komplexa (" spelare” i ett onlinespel ) objekt .
Så här ser minimifunktionsmallen ut:
mall < typnamnT > _ T min ( T a , T b ) { returnera a < b ? a : b ; }För att anropa den här funktionen kan du helt enkelt använda dess namn:
min ( 1,2 ) ; _ min ( 'a' , 'b' ); min ( sträng ( "abc" ), sträng ( "cde" ) );Generellt sett, för att anropa en mallfunktion måste du ange värden för alla mallparametrar. För att göra detta, efter mallnamnet, anges en lista med värden inom vinkelparenteser:
int i [] = { 5 , 4 , 3 , 2 , 1 }; sortera < int > ( i , 5 ); char c [] = "bvgda" ; sortera < char > ( c , strlen ( c ) ); sortera < int > ( c , 5 ); // error: sort<int> har en int[]-parameter, inte ett char[] char * ReadString = läs < 20 > (); ta bort [] ReadString ; ReadString = läs < 30 > ();För varje uppsättning alternativ genererar kompilatorn en ny instans av funktionen. Processen att skapa en ny instans kallas mallinstansiering .
I exemplet ovan skapade kompilatorn två funktionsmallspecialiseringar sort(för typerna charoch int) och två mallspecialiseringar read(för värdena BufferSize20 och 30). Det senare är med största sannolikhet slösaktigt, eftersom kompilatorn för varje möjligt värde på parametern kommer att skapa fler och fler nya instanser av funktioner som bara skiljer sig med en konstant.
Härledning av parametervärdenI vissa fall kan kompilatorn härleda (logiskt bestämma) värdet på en funktionsmallparameter från ett funktionsargument. Till exempel, när du anropar funktionen som beskrivs ovan, är det sortinte nödvändigt att ange mallparametern (om den matchar typen av elementen i arrayargumentet):
int i [ 5 ] = { 5 , 4 , 3 , 2 , 1 }; sortera ( i , 5 ); // ring sortera<int> char c [] = "bvgda" ; sortera ( c , strlen ( c ) ); // ring sortera<char>Borttagning är också möjligt i mer komplexa fall .
I fallet med användning av klassmallar med heltalsparametrar är det också möjligt att härleda dessa parametrar. Till exempel:
mall < int storlek > klass IntegerArray { int Array [ storlek ]; /* ... */ }; mall < int size > // Template prototype void PrintArray ( IntegerArray < size > array ) { /* ... */ } // Template call // Använda mallobjektet IntegerArray < 20 > ia ; PrintArray ( ia );Slutledningsregler introduceras i språket för att göra det lättare att använda en mall och för att undvika eventuella fel, som att försöka sort< int >sortera en rad tecken.
Om en mallparameter kan härledas från flera argument, måste resultatet av slutledningen vara exakt detsamma för alla dessa argument. Till exempel är följande anrop felaktiga:
min ( 0 , 'a' ); min ( 7 , 7,0 );Fel associerade med användningen av specifika mallparametrar kan inte upptäckas innan mallen används. Till exempel mininnehåller själva mallen inga fel, men att använda den med typer för vilka operationen '<'inte är definierad kommer att resultera i ett fel:
struktur A { int a ; }; A obj1 , obj2 ; min ( objl , obj2 );Om du går in i operationen '<'före den första användningen av mallen, kommer felet att elimineras. Så här visar sig flexibiliteten hos mallar i C++ :
friend inline bool operator < ( const A & a1 , const A & a2 ) { return a1 . a < a2 . a ; } min ( objl , obj2 );I en klass som implementerar en länkad lista med heltal, beror algoritmerna för att lägga till ett nytt element i listan och söka efter det önskade elementet inte av att elementen i listan är heltal. Samma algoritmer skulle gälla för en lista med karaktärer, strängar, datum, spelarklasser och så vidare.
mall < classT > _ klasslista _ { /* ... */ offentliga : void Lägg till ( const T & Element ); bool Hitta ( const T & Element ); /* ... */ };För att använda en klassmall måste du ange dess parametrar:
Lista < int > li ; Lista < sträng > ls ; li . lägg till ( 17 ); ls . Lägg till ( "Hej!" );Mallparametrar kan vara: typparametrar, vanliga typparametrar, mallparametrar.
Du kan ange standardvärden för parametrar av vilken typ som helst.
mall < klass T1 , // typ parameter typnamn T2 , // typ parameter int I , // normal typ parameter T1 DefaultValue , // normal typ parameter mall < klass > klass T3 , // mall parameter klass Character = char // default parameter > MallparametrarOm det är nödvändigt att använda samma mall i en klass- eller funktionsmall, men med olika parametrar, används mallparametrar. Till exempel:
mall < klass Typ , mall < klass > klass Container > klass CrossReferences { Behållare < Typ > mems ; Behållare < Typ * > refs ; /* ... */ }; Korsreferenser < Datum , vektor > cr1 ; CrossReferences < string , set > cr2 ;Funktionsmallar kan inte användas som mallparametrar.
För parametrar som är typer (till exempel T-parametern för sorteringsfunktionen) är slutledning möjlig om funktionsargumentet är av en av följande typer:
Typ av argument | Beskrivning |
---|---|
T const T volatile T |
Själva typen T, eventuellt med modifierare consteller volatile. mall < classT > _ T ReturnMe ( const T arg ) { return arg ; } ReturnMe ( 7 ); ReturnMe ( 'a' ); |
T* T& T[A] A är en konstant |
En pekare, referens eller array av element av typen T.
Ett exempel är sorteringsfunktionsmallen som diskuteras ovan. |
Templ<T> Templ - klassmallnamn |
Som ett argument kräver funktionen en specifik specialisering av någon mall. #inkludera <vektor> mall < classT > _ void sort ( vektor < T > array ) { /* sort */ } vektor < int > i ; vektor < char > c ; sortera ( i ); sortera ( c ); |
T (*) (args) args - några argument |
Pekare till en funktion som returnerar typ T. mall < classT > _ T * CreateArray ( T ( * GetValue )(), const int size ) { T * Array = ny T [ storlek ]; för ( int i = 0 ; i < storlek ; i ++ ) Array [ i ] = GetValue (); return Array ; } int GetZero () { return 0 ; } char InputChar () { röding c ; cin >> c ; returnera c ; } int * ArrayOfZeros = CreateArray ( GetZero , 20 ); char * String = CreateArray ( InputChar , 40 ); |
type T::* T Class::* typ - någon typ Klass - någon klass |
En pekare till en medlem av klassen T av en godtycklig typ. Pekare till en medlem av typ T av en godtycklig klass. klass MyClass { offentliga : int a ; }; mall < classT > _ T & IncrementIntegerElement ( int T ::* Element , T & Object ) { objekt . * Element += 1 ; returnera Objekt ; } mall < classT > _ T IncrementMyClassElement ( T MyClass ::* Element , MyClass & Object ) { objekt . * Element += 1 ; returnera objekt . * Element ; } MyClass Obj ; int n ; n = ( IncrementIntegerElement ( & MyClass :: a , Obj ) ). a ; n = IncrementMyClassElement ( & MyClass :: a , Obj ); |
type (T::*) (args) T (Class::*) (args) typ - någon typ Klass - någon klass args - några argument |
Pekare till en medlemsfunktion av klass T av godtycklig typ. Pekare till en medlemsfunktion av typ T av en godtycklig klass. klass MyClass { offentliga : int a ; int IncrementA (); }; int MyClass::IncrementA () { return ++ a ; } mall < classT > _ T & CallIntFunction ( int ( T ::* Function )(), T & Object ) { ( Objekt . * Funktion )(); returnera Objekt ; } mall < classT > _ T CallMyClassFunction ( T ( MyClass ::* Function )(), MyClass & Object ) { return ( Objekt . * Funktion )(); } MyClass Obj ; int n ; n = ( CallIntFunction ( & MyClass :: IncrementA , Obj ) ). a ; n = CallMyClassFunction ( & MyClass :: IncrementA , Obj ); |
Medlemmar av en klassmall är mallar, och med samma parametrering som klassmallen. Detta innebär i synnerhet att definitionen av medlemsfunktioner bör börja med mallhuvudet:
mall < classT > _ klass A { void f ( T- data ); void g ( void ); offentliga : A (); }; mall < classT > _ void A < T >:: f ( T data ); mall < classT > _ void A < T >:: g ( void );Inom mallens räckvidd behöver specifikationen inte upprepas. Det betyder att till exempel A<T>::A() är en konstruktor , även om du också kan skriva A<T>::A<T>().
Typer som medlemmar i klasserOm mallparametern är en klass som har en medlem som är av datatyp , måste nyckelordet användas för att använda den medlemmen typename. Till exempel:
klass Container { offentliga : int array [ 15 ]; typedef int * iterator ; /* ... */ iterator start () { return array ; } }; mall < klass C > void f ( C & vektor ) { C :: iterator i = vektor . börja (); // fel typnamn C :: iterator i = vektor . börja (); } Mallar som medlemmar i klasserDet finns också problem med mallmedlemmar. Om en mall (ConvertTo()), som är medlem i en klass (A), som i sin tur är en mallparameter (f), används i denna mall (f) och inte tillåter inferens av parametrar, då måste användas template:
klass A { /* ... */ offentliga : mall < klass T > T & ConvertTo (); mall < klass T > void ConvertFrom ( const T & data ); }; mall < classT > _ void f ( T Container ) { int i1 = Behållare . mall ConvertTo < int > () + 1 ; behållare . ConvertFrom ( i1 ); // ingen kvalificering behövs }Mallmetaprogrammering i C++ lider av många begränsningar, inklusive portabilitetsproblem, brist på felsökning eller I/O-stöd under mallinstansiering, långa kompileringstider, dålig kodläsbarhet, dålig feldiagnostik och oklara felmeddelanden [2] . C++ mallundersystemet definieras som ett Turing-komplett rent funktionellt programmeringsspråk, men programmerare i funktionell stil ser detta som en provokation och är ovilliga att erkänna C++ som ett framgångsrikt språk [3] .
Många språk ( Java 5, Ada , Delphi 2009) implementerar generiskt programmeringsstöd på ett enklare sätt, några till och med på typsystemnivå (se Eiffel och parametrisk polymorfism i ML- familjen av språk ); sådana språk behöver inte mekanismer som liknar C++-mallar.
C:s makroersättningsfaciliteter, även om de inte är kompletta med Turing, är tillräckliga för lågnivåprogrammering i generativ programmering , och deras kapacitet har utökats avsevärt i C99 .
D - språket har mallar som är kraftfullare än C++. [4] .
Datatyper | |
---|---|
Otolkbart | |
Numerisk | |
Text | |
Referens | |
Sammansatt | |
abstrakt | |
Övrig | |
Relaterade ämnen |