C++11 [1] [2] tai ISO/IEC 14882:2011 [3] (standardia kehitettäessä sen koodinimi oli C++0x [4] [5] ) – uusi versio C++ -kielistandardi aiemmin voimassa olevan ISO /IEC 14882:2003:n sijaan . Uusi standardi sisältää lisäyksiä kielen ytimeen ja laajennuksen standardikirjastoon, mukaan lukien suurin osa TR1 :stä - paitsi ehkä erityisten matemaattisten funktioiden kirjasto. Standardien uudet versiot sekä eräät muut C++-standardointiasiakirjat julkaistaan ISO C++ -komitean verkkosivustolla [6] . C++ ohjelmointiesimerkkejä
Ohjelmointikielten kykyjä kehitetään asteittain (tällä hetkellä C++11:n jälkeen on julkaistu seuraavat vakiolaajennukset: C++14, C++17, C++20). Tämä prosessi aiheuttaa väistämättä yhteensopivuusongelmia olemassa olevan koodin kanssa. Kansainvälisen standardin N3290 lopullisen luonnoksen liitteessä C.2 [diff.cpp03] kuvataan joitakin C++11:n ja C++03 :n välisiä ristiriitoja.
Kuten jo mainittiin, muutokset vaikuttavat sekä C++ -ytimeen että sen vakiokirjastoon.
Tulevan standardin kutakin osaa kehittäessään komitea käytti useita sääntöjä:
Huomiota kiinnitetään aloittelijoihin, jotka muodostavat aina suurimman osan ohjelmoijista. Monet aloittelijat eivät pyri syventämään C ++ -taitojaan, vaan rajoittuvat käyttämään sitä tehdessään kapeita erityistehtäviä [7] . Lisäksi, kun otetaan huomioon C++:n monipuolisuus ja sen käyttömahdollisuudet (mukaan lukien sekä erilaiset sovellukset että ohjelmointityylit), ammattilaisetkin voivat löytää itselleen uusia ohjelmointiparadigmoja .
Toimikunnan ensisijaisena tehtävänä on kehittää C++-kielen ydin. Ydintä on parannettu merkittävästi, monisäikeinen tuki on lisätty, yleisohjelmoinnin tukea on parannettu , alustus on yhtenäistetty ja sen suorituskyvyn parantamiseksi on tehty työtä.
Mukavuuden vuoksi ytimen ominaisuudet ja muutokset on jaettu kolmeen pääosaan: suorituskyvyn parannukset, käyttömukavuuden parannukset ja uudet toiminnot. Yksittäiset elementit voivat kuulua useisiin ryhmiin, mutta niitä kuvataan vain yhdessä - sopivimmassa.
Nämä kielikomponentit otetaan käyttöön muistin vähentämiseksi tai suorituskyvyn parantamiseksi.
Väliaikaiset objektiviittaukset ja siirtosemantiikkaC++-standardin mukaan väliaikainen olio , joka syntyy lausekkeen arvioinnista, voidaan välittää funktioille, mutta vain vakioviittauksella ( const & ). Funktio ei pysty määrittämään, voidaanko ohitettua objektia pitää väliaikaisena ja muokattavana (const-objektia, joka voidaan myös välittää tällaisella viittauksella, ei voida muokata (laillisesti)). Tämä ei ole ongelma yksinkertaisissa rakenteissa, kuten complex, mutta monimutkaisissa tyypeissä, jotka vaativat muistin varausta ja purkamista, väliaikaisen objektin tuhoaminen ja pysyvän luominen voi olla aikaa vievää, kun taas osoittimia voi yksinkertaisesti välittää suoraan.
C++11 esittelee uudentyyppisen viittauksen , rvalue - viittauksen . Sen ilmoitus on: type && . Uudet ylikuormitusresoluutiosäännöt mahdollistavat erilaisten ylikuormitettujen funktioiden käyttämisen ei-const-väliaikaisille objekteille, jotka on merkitty rarvoilla, ja kaikille muille objekteille. Tämä innovaatio mahdollistaa niin kutsutun liikkuvan semantiikan toteuttamisen .
Esimerkiksi std::vector on yksinkertainen kääre C-taulukon ympärille ja muuttuja, joka tallentaa sen koon. Kopiointikonstruktori std::vector::vector(const vector &x)luo uuden taulukon ja kopioi tiedot; siirtokonstruktori std::vector::vector(vector &&x)voi yksinkertaisesti vaihtaa pituuden sisältäviä osoittimia ja muuttujia.
Mainosesimerkki.
malli < luokka T > luokkavektori _ { vektori ( const vektori & ); // Kopioi konstruktori (hidas) vektori ( vektori && ); // Konstruktorin siirto väliaikaisesta objektista (nopea) vektori & operaattori = ( const vektori & ); // Säännöllinen osoitus (hidas) vektori & operaattori = ( vektori && ); // Siirrä väliaikainen objekti (nopea) void foo () & ; // Funktio, joka toimii vain nimetylle objektille (hidas) void foo () && ; // Toiminto, joka toimii vain väliaikaiselle objektille (nopea) };Väliaikaisiin linkkeihin liittyy useita malleja, joista kaksi tärkeintä ovat ja . Ensimmäinen tekee tavallisesta nimetystä objektista väliaikaisen viittauksen: moveforward
// std::siirrä malliesimerkki void bar ( std :: string && x ) { staattinen std :: stringsomeString ; _ someString = std :: siirrä ( x ); // x=string&-funktion sisällä, joten toinen siirto siirtotehtävän kutsumiseksi } std :: stringy ; _ bar ( std :: siirrä ( y )); // ensimmäinen siirto muuttaa merkkijonon& merkkijonoksi&& kutsupalkkiinMallia käytetään vain metaohjelmoinnissa, se vaatii eksplisiittisen malliparametrin (sillä on kaksi erottamatonta ylikuormitusta) ja se liittyy kahteen uuteen C++-mekanismiin. Ensimmäinen on linkkien liimaus: , sitten . Toiseksi yllä oleva bar()-funktio vaatii väliaikaisen objektin ulkopuolella, mutta sisäpuolella x-parametri on tavallinen nimetty (lvalue) varalle, mikä tekee mahdottomaksi erottaa automaattisesti string&-parametria merkkijono&&-parametrista. Tavallisessa ei-mallifunktiossa ohjelmoija voi laittaa tai jättää liikkumatta, mutta entä malli? forwardusing One=int&&; using Two=One&;Two=int&
// esimerkki mallin käytöstä std::forward class Obj { std :: stringfield ; _ malli < luokkaT > _ Obj ( T && x ) : kenttä ( std :: eteenpäin < T > ( x )) {} };Tämä konstruktori kattaa tavalliset (T=string&), kopiointi (T=const string&) ja siirto (T=string) ylikuormitukset referenssiliimauksella. Ja eteenpäin ei tee mitään tai laajenee muotoon std::move riippuen T:n tyypistä, ja rakentaja kopioi, jos se on kopio, ja siirtää, jos se on siirto.
Yleiset vakiolausekkeetC++:lla on aina ollut vakiolausekkeiden käsite. Näin ollen lausekkeet, kuten 3+4 , palauttivat aina samat tulokset aiheuttamatta sivuvaikutuksia. Itse vakiolausekkeet tarjoavat kätevän tavan C++-kääntäjille optimoida kääntämisen tulos. Kääntäjät arvioivat tällaisten lausekkeiden tulokset vasta käännöshetkellä ja tallentavat jo lasketut tulokset ohjelmaan. Siten tällaiset lausekkeet arvioidaan vain kerran. On myös muutamia tapauksia, joissa kielistandardi edellyttää vakiolausekkeiden käyttöä. Tällaisia tapauksia voivat olla esimerkiksi ulkoisten taulukoiden tai enum-arvojen määritelmät.
Yllä oleva koodi on laitonta C++:ssa, koska GiveFive() + 7 ei ole teknisesti käännöshetkellä tunnettu vakiolauseke. Kääntäjä ei vain tiedä tuolloin, että funktio todella palauttaa vakion ajon aikana. Syy tähän kääntäjän päättelyyn on, että tämä funktio voi vaikuttaa globaalin muuttujan tilaan, kutsua toista ei-vakiota ajonaikaista funktiota ja niin edelleen.
C++11 ottaa käyttöön avainsanan constexpr , jonka avulla käyttäjä voi varmistaa, että joko funktio tai objektikonstruktori palauttaa käännösaikavakion. Yllä oleva koodi voidaan kirjoittaa uudelleen seuraavasti:
constexpr int GiveFive () { return 5 ;} int jokin_arvo [ AnnaFive () + 7 ]; // luo 12 kokonaisluvun taulukko; sallittu C++11:ssäTämän avainsanan avulla kääntäjä voi ymmärtää ja varmistaa, että GiveFive palauttaa vakion.
Constexprin käyttö asettaa erittäin tiukat rajoitukset funktion toimiin:
Standardin aiemmassa versiossa vakiolausekkeissa voitiin käyttää vain kokonaisluku- tai enum-tyyppisiä muuttujia. C++11:ssä tämä rajoitus poistetaan muuttujilta, joiden määritelmää edeltää avainsana constexpr:
constexpr double accelerationOfGravity = 9,8 ; constexpr double moonGravity = accelrationOfGravity / 6 ;Tällaisten muuttujien katsotaan jo implisiittisesti olevan const - avainsanalla . Ne voivat sisältää vain vakiolausekkeiden tulokset tai tällaisten lausekkeiden konstruktorit.
Jos on tarpeen rakentaa vakioarvoja käyttäjän määrittämistä tyypeistä, tällaisten tyyppien konstruktorit voidaan myös ilmoittaa käyttämällä constexpr . Vakiolausekkeen konstruktori, kuten vakiofunktiot, on myös määritettävä ennen sen ensimmäistä käyttöä nykyisessä käännösyksikössä. Tällaisella konstruktorilla tulee olla tyhjä runko, ja sellaisen konstruktorin tulee alustaa tyyppinsä jäsenet vain vakioilla.
Muutoksia yksinkertaisen tiedon määritelmäänNormaalissa C++:ssa vain rakenteita, jotka täyttävät tietyt säännöt, voidaan pitää tavallisena vanhana tietotyyppinä ( POD). On hyviä syitä odottaa, että näitä sääntöjä laajennetaan niin, että useampia tyyppejä katsotaan POD:iksi. Nämä säännöt täyttäviä tyyppejä voidaan käyttää C-yhteensopivassa objektikerroksen toteutuksessa, mutta C++03:n luettelo näistä säännöistä on liian rajoittava.
C++11 lieventää useita yksinkertaisten tietotyyppien määrittelyä koskevia sääntöjä.
Luokkaa pidetään yksinkertaisena tietotyyppinä, jos se on triviaali , sillä on vakioasettelu ( standard-layout ) ja jos kaikkien sen ei-staattisten tietojäsenten tyypit ovat myös yksinkertaisia tietotyyppejä.
Triviaaliluokka on luokka, joka:
Normaalisijoitusluokka on luokka, joka:
Normaalissa C++:ssa kääntäjän on luotava malli aina, kun se kohtaa täyden erikoistumisensa käännösyksikköön. Tämä voi pidentää merkittävästi käännösaikaa, varsinkin kun malliin instantioidaan samoilla parametreilla useissa käännösyksiköissä. Tällä hetkellä ei ole mitään keinoa kertoa C++:lle, ettei ilmentymistä pitäisi olla.
C++11 esitteli ajatuksen ulkoisista malleista. C++:ssa on jo syntaksi, joka kertoo kääntäjälle, että malli tulee instantoida tietyssä vaiheessa:
mallin luokka std :: vektori < MyClass > ;C++:lta puuttuu kyky estää kääntäjää luomasta mallia käännösyksikössä. C++11 yksinkertaisesti laajentaa tätä syntaksia:
extern template class std :: vektori < MyClass > ;Tämä lauseke käskee kääntäjää olemaan instantoimatta mallia tässä käännösyksikössä.
Nämä ominaisuudet on tarkoitettu helpottamaan kielen käyttöä. Niiden avulla voit vahvistaa tyyppiturvallisuutta, minimoi koodin päällekkäisyyksiä, vaikeuttaa koodin väärinkäyttöä ja niin edelleen.
AlustusluettelotAlustuslistojen käsite tuli C++:aan C:stä. Ajatuksena on, että rakenne tai taulukko voidaan luoda välittämällä argumenttiluettelo samassa järjestyksessä kuin rakenteen jäsenet määritellään. Alustuslistat ovat rekursiivisia, mikä mahdollistaa niiden käyttämisen rakenteille ja sisäkkäisiä rakenteita sisältäville rakenteille.
rakenneobjekti _ { kellua ensin ; int toinen ; }; Objektin skalaari = { 0,43f , 10 }; // yksi objekti, jossa ensimmäinen=0.43f ja toinen=10 Object anArray [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // kolmen objektin joukkoAlustusluettelot ovat erittäin hyödyllisiä staattisille listoille ja kun haluat alustaa rakenteen tiettyyn arvoon. C++ sisältää myös konstruktoreita, jotka voivat sisältää yleisen objektien alustustyön. C++-standardi sallii alustusluetteloiden käytön rakenteille ja luokille, mikäli ne ovat POD (Plain Old Data) -määritelmän mukaisia. Muut kuin POD-luokat eivät voi käyttää alustusluetteloita alustukseen, mukaan lukien standardi C++ -säiliöt, kuten vektorit.
C++11 on liittänyt käsitteen alustusluettelot ja malliluokka nimeltä std::initializer_list . Tämä antoi rakentajille ja muille funktioille mahdollisuuden vastaanottaa alustusluetteloita parametreina. Esimerkiksi:
luokka SequenceClass { julkinen : SequenceClass ( std :: alustusluettelo < int > lista ); };Tämän kuvauksen avulla voit luoda SequenceClass -luokan kokonaislukujen sarjasta seuraavasti:
SequenceClass someVar = { 1 , 4 , 5 , 6 };Tämä osoittaa, kuinka erityinen konstruktori toimii alustusluettelossa. Tällaisia rakentajia sisältäviä luokkia käsitellään erityisellä tavalla alustuksen aikana (katso alla ).
Std ::initializer_list<> -luokka on määritelty C++11-standardikirjastossa. Tämän luokan objekteja voi kuitenkin luoda staattisesti vain C++11-kääntäjällä {} hakasulkeiden syntaksilla. Luettelo voidaan kopioida luomisen jälkeen, mutta tämä kopioidaan viitteellä. Alustuslista on vakio: sen jäseniä tai niiden tietoja ei voi muuttaa luomisen jälkeen.
Koska std::initializer_list<> on täysi tyyppi, sitä voidaan käyttää muussakin kuin vain rakentajissa. Tavalliset funktiot voivat ottaa kirjoitetut alustusluettelot argumenttina, esimerkiksi:
void FunctionName ( std :: alustusluettelo < float > lista ); FunctionName ({ 1.0f , -3.45f , -0.4f });Vakiosäiliöt voidaan alustaa seuraavasti:
std :: vektori < std :: merkkijono > v = { "xyzzy" , "plugh" , "abracadabra" }; std :: vektori < std :: string > v { "xyzzy" , "plugh" , "abracadabra" }; Yleinen alustusC++-standardi sisältää useita tyypin alustukseen liittyviä ongelmia. On olemassa useita tapoja alustaa tyyppejä, eivätkä ne kaikki johda samoihin tuloksiin. Esimerkiksi alustavan rakentajan perinteinen syntaksi saattaa näyttää funktion määrittelyltä, ja on oltava erityisen huolellinen, jotta kääntäjä ei jäsentä sitä väärin. Vain aggregaattityyppejä ja POD-tyyppejä voidaan alustaa koontialustajalla (tyyppisillä SomeType var = {/*stuff*/};).
C++11 tarjoaa syntaksin, joka mahdollistaa yhden alustuksen muodon käytön kaikenlaisille objekteille laajentamalla alustusluettelon syntaksia:
struct BasicStruct { int x ; kaksinkertainen y ; }; struct AltStruct { AltStruct ( int x , double y ) : x_ ( x ), y_ ( y ) {} yksityinen : int x_ ; kaksinkertainen y_ ; }; BasicStruct var1 { 5 , 3.2 }; AltStruct var2 { 2 , 4.3 };Var1 :n alustus toimii täsmälleen samalla tavalla kuin aggregaattien alustus, eli jokainen objekti alustetaan kopioimalla vastaava arvo alustusluettelosta. Tarvittaessa käytetään implisiittistä tyyppimuunnosa. Jos haluttua muunnosa ei ole, lähdekoodia pidetään virheellisenä. Var2 : n alustuksen aikana konstruktoria kutsutaan.
Koodi on mahdollista kirjoittaa näin:
struct IdString { std :: merkkijonon nimi ; _ int tunniste ; }; IdString GetString () { return { "SomeName" , 4 }; // Huomaa selkeän tyyppien puute }Yleinen alustus ei täysin korvaa rakentajan alustussyntaksia. Jos luokassa on konstruktori, joka ottaa alustusluettelon ( TyypinNimi(alustajaluettelo<JokinTyyppi>); ) argumenttina, se on etusijalla muihin objektin luontivaihtoehtoihin nähden. Esimerkiksi C++11 :ssä std::vector sisältää konstruktorin, joka ottaa alustusluettelon argumenttina:
std :: vektori < int > theVec { 4 };Tämä koodi johtaa konstruktorikutsuun, joka käyttää alustusluetteloa argumenttina yhden parametrin rakentajan sijaan, joka luo tietyn kokoisen säilön. Tämän rakentajan kutsumiseksi käyttäjän on käytettävä rakentajan kutsusyntaksia.
TyyppipäätelmäStandardissa C++ (ja C) muuttujan tyyppi on määritettävä eksplisiittisesti. Mallityyppien ja mallien metaohjelmointitekniikoiden myötä joidenkin arvojen tyyppiä, erityisesti funktion palautusarvoja, ei kuitenkaan voida määrittää helposti. Tämä johtaa vaikeuksiin välitietojen tallentamisessa muuttujiin, joskus voi olla tarpeen tuntea tietyn metaohjelmointikirjaston sisäinen rakenne.
C++11 tarjoaa kaksi tapaa lieventää näitä ongelmia. Ensinnäkin eksplisiittisesti alustettavan muuttujan määritelmä voi sisältää automaattisen avainsanan . Tämä johtaa alustusarvon tyyppisen muuttujan luomiseen:
auto someStrangeCallableType = std :: bind ( & SomeFunction , _2 , _1 , someObject ); auto otherVariable = 5 ;Tyypistä someStrangeCallableType tulee tyyppi, jonka mallifunktion konkreettinen toteutus palauttaa std::bindannetuille argumenteille. Kääntäjä määrittää tämän tyypin helposti semanttisen analyysin aikana, mutta ohjelmoijan on tehtävä tutkimusta tyypin määrittämiseksi.
Myös OtherVariable -tyyppi on hyvin määritelty, mutta ohjelmoija voi yhtä helposti määrittää sen. Tämä tyyppi on int , sama kuin kokonaislukuvakio.
Lisäksi decltype- avainsanaa voidaan käyttää määrittämään lausekkeen tyyppi käännöshetkellä . Esimerkiksi:
int someInt ; decltype ( someInt ) otherIntegerVariable = 5 ;Decltype :n käyttö on hyödyllisintä yhdessä auto :n kanssa , koska muuttujan tyyppi, joka on ilmoitettu autoksi , on vain kääntäjän tiedossa . Myös decltypen käyttö voi olla varsin hyödyllistä lausekkeissa, jotka käyttävät operaattorin ylikuormitusta ja mallien erikoistumista.
autovoidaan käyttää myös koodin redundanssin vähentämiseen. Esimerkiksi sen sijaan:
for ( vektori < int >:: const_iterator itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )ohjelmoija osaa kirjoittaa:
for ( auto itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )Ero tulee erityisen havaittavaksi, kun ohjelmoija käyttää suurta määrää erilaisia säiliöitä, vaikka silti on olemassa hyvä tapa vähentää ylimääräistä koodia typedef.
Decltype -merkillä merkitty tyyppi voi olla erilainen kuin auto .
#sisällytä <vektori> int main () { const std :: vektori < int > v ( 1 ); auto a = v [ 0 ]; // kirjoita a - int decltype ( v [ 0 ]) b = 1 ; // tyyppi b - const int& (palautusarvo // std::vektori<int>::operaattori[](kokotyyppi) const) auto c = 0 ; // kirjoita c - int auto d = c ; // tyyppi d - int decltype ( c ) e ; // kirjoita e - int, entiteetin tyyppi nimeltä c decltype (( c )) f = c ; // tyyppi f on int& koska (c) on lvvalue decltype ( 0 ) g ; // tyyppi g on int, koska 0 on rarvo } For-silmukka kokoelman läpiNormaalissa C++ :ssa kokoelman elementtien iterointi vaatii paljon koodia . Joissakin kielissä, kuten C# , on toiminnot, jotka tarjoavat " foreach " -lausekkeen , joka käy automaattisesti läpi kokoelman elementit alusta loppuun. C++11 esittelee samanlaisen toiminnon. For- lause helpottaa elementtikokoelman iterointia:
int oma_taulukko [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; for ( int & x : my_array ) { x *= 2 ; }Tämä for-muoto, jota kutsutaan englanniksi "range-based for", vierailee kokoelman jokaisessa elementissä. Tämä koskee C - taulukoita , alustusluetteloita ja kaikkia muita tyyppejä , joissa on toimintoja begin()ja end()jotka palauttavat iteraattoreita . Kaikki vakiokirjaston säilöt , joilla on alku/loppu-pari, toimivat for-lausekkeen kanssa kokoelmassa.
Tällainen sykli toimii myös esimerkiksi C-tyyppisten taulukoiden kanssa, koska C++11 esittelee keinotekoisesti heille tarvittavat pseudomenetelmät (alku, loppu ja jotkut muut).
// klassisen taulukon aluepohjainen läpikulku int arr1 [] = { 1 , 2 , 3 }; for ( auto el : arr1 ); Lambdafunktiot ja lausekkeetNormaalissa C++:ssa esimerkiksi käytettäessä tavallisia C++-kirjastoalgoritmeja lajittele ja löydä , on usein tarve määritellä predikaattifunktiot lähellä algoritmin kutsua. Tätä varten kielessä on vain yksi mekanismi: kyky määritellä funktionaaliluokka (funktion sisällä määritellyn luokan esiintymän välittäminen algoritmeille on kielletty (Meyers, Effective STL)). Usein tämä menetelmä on liian redundantti ja monisanainen ja vain vaikeuttaa koodin lukemista. Lisäksi funktioissa määriteltyjen luokkien standardit C++-säännöt eivät salli niiden käyttämistä malleissa ja tekevät niiden käytöstä siten mahdotonta.
Ilmeinen ratkaisu ongelmaan oli sallia lambda-lausekkeiden ja lambda-funktioiden määrittely C++11:ssä. Lambda-funktio määritellään seuraavasti:
[]( int x , int y ) { return x + y ; }Tämän nimettömän funktion palautustyyppi lasketaan muodossa decltype(x+y) . Palautustyyppi voidaan jättää pois vain, jos lambda-funktio on muotoa . Tämä rajoittaa lambda-funktion koon yhteen lausekkeeseen. return expression
Palautustyyppi voidaan määrittää eksplisiittisesti, esimerkiksi:
[]( int x , int y ) -> int { int z = x + y ; paluu z ; }Tämä esimerkki luo väliaikaisen muuttujan z väliarvon tallentamiseksi. Kuten tavallisten toimintojen kohdalla, tämä väliarvo ei säily puheluiden välillä.
Palautustyyppi voidaan jättää kokonaan pois, jos funktio ei palauta arvoa (eli palautustyyppi on void )
On myös mahdollista käyttää viittauksia muuttujiin, jotka on määritelty samassa laajuudessa kuin lambda-funktio. Tällaisten muuttujien joukkoa kutsutaan yleensä sulkemiseksi . Sulkemiset määritellään ja niitä käytetään seuraavasti:
std :: vektori < int > someList ; kokonaissumma = 0 ; _ std :: for_each ( someList . begin (), someList . end (), [ & total ]( int x ) { yhteensä += x ; }); std :: cout << yhteensä ;Tämä näyttää luettelon kaikkien elementtien summan. Kokonaismuuttuja tallennetaan osana lambda-funktion sulkemista . Koska se viittaa pinomuuttujaan total , se voi muuttaa sen arvoa.
Paikallisten muuttujien sulkemismuuttujat voidaan määrittää myös ilman viitesymbolia & , mikä tarkoittaa, että funktio kopioi arvon. Tämä pakottaa käyttäjän ilmoittamaan aikomuksensa viitata paikalliseen muuttujaan tai kopioida sitä.
Lambda-funktioissa, joiden suoritus on taattu laajuudessaan, on mahdollista käyttää kaikkia pinomuuttujia ilman erityisiä viittauksia niihin:
std :: vektori < int > someList ; kokonaissumma = 0 ; _ std :: for_each ( someList . begin (), someList . end (), [ & ]( int x ) { yhteensä += x ; });Toteutustavat voivat vaihdella sisäisesti, mutta lambda-funktion odotetaan tallentavan osoittimen sen funktion pinoon, jossa se luotiin, sen sijaan, että se toimisi yksittäisten pinomuuttujien viittausten perusteella.
Jos sen sijaan [&]käytetään [=], kaikki käytetyt muuttujat kopioidaan, jolloin lambda-funktiota voidaan käyttää alkuperäisten muuttujien ulkopuolella.
Oletussiirtotapaa voidaan myös täydentää yksittäisten muuttujien luettelolla. Jos sinun on esimerkiksi välitettävä suurin osa muuttujista viitteellä ja yksi arvolla, voit käyttää seuraavaa rakennetta:
kokonaissumma = 0 ; _ int arvo = 5 ; [ & , arvo ]( int x ) { yhteensä += ( x * arvo ); } ( 1 ); //(1) kutsu lambda-funktiota arvolla 1Tämä aiheuttaa sen, että summa välitetään viitteellä ja arvo arvolla.
Jos lambda-funktio on määritelty luokkametodissa, sitä pidetään kyseisen luokan ystävänä. Tällaiset lambda-funktiot voivat käyttää viittausta luokkatyyppiseen objektiin ja käyttää sen sisäisiä kenttiä:
[]( SomeType * typePtr ) { typePtr -> SomePrivateMemberFunction (); }Tämä toimii vain, jos lambda-funktion laajuus on luokkamenetelmä SomeType .
Työskentely tämän osoittimen kanssa objektiin, jonka kanssa nykyinen menetelmä on vuorovaikutuksessa, toteutetaan erityisellä tavalla. Se on merkittävä nimenomaisesti lambda-funktioon:
[ this ]() { this -> SomePrivateMemberFunction (); }Lomakkeen [&]tai [=]lambda-toiminnon avulla tämä on käytettävissä automaattisesti.
Lambda-toimintojen tyyppi riippuu toteutuksesta; tämän tyypin nimi on vain kääntäjän käytettävissä. Jos sinun on välitettävä lambda-funktio parametrina, sen on oltava mallityyppiä tai tallennettu std::funktiolla . Automaattisen avainsanan avulla voit tallentaa lambda-funktion paikallisesti:
auto myLambdaFunc = [ this ]() { this -> SomePrivateMemberFunction (); };Lisäksi, jos funktio ei ota argumentteja, ()voit jättää pois:
auto myLambdaFunc = []{ std :: cout << "hei" << std :: endl ; }; Vaihtoehtoisen funktion syntaksiJoskus on tarpeen toteuttaa funktiomalli, joka johtaisi lausekkeeseen, jolla on sama tyyppi ja sama arvoluokka kuin jollain muulla lausekkeella.
malli < typename LHS , typename RHS > RETURN_TYPE AddingFunc ( const LHS & lhs , const RHS & rhs ) // mitä RETURN_TYPE pitäisi olla? { paluu lhs + rhs ; }Jotta lausekkeella AddingFunc(x, y) olisi sama tyyppi ja sama arvoluokka kuin lausekkeella lhs + rhs , kun argumentit x ja y annettaisiin , C++11:ssä voidaan käyttää seuraavaa määritelmää:
malli < typename LHS , typename RHS > decltype ( std :: declval < const LHS &> () + std :: declval < const RHS &> ()) AddingFunc ( const LHS & lhs , const RHS & rhs ) { paluu lhs + rhs ; }Tämä merkintä on hieman hankala, ja olisi mukavaa käyttää lhs- ja rhs-arvoja std::declval<const LHS &>() ja std::declval<const RHS &>() sijasta. Kuitenkin seuraavassa versiossa
malli < typename LHS , typename RHS > decltype ( lhs + rhs ) AddingFunc ( const LHS & lhs , const RHS & rhs ) // Ei voimassa C++11 { paluu lhs + rhs ; }Ihmisen luettavampi, decltype-operandissa käytetyt lhs- ja rhs-tunnisteet eivät voi merkitä myöhemmin ilmoitettuja vaihtoehtoja. Tämän ongelman ratkaisemiseksi C++11 ottaa käyttöön uuden syntaksin funktioiden ilmoittamiseen, jonka lopussa on palautustyyppi:
malli < typename LHS , typename RHS > auto AddingFunc ( const LHS & lhs , const RHS & rhs ) -> decltype ( lhs + rhs ) { paluu lhs + rhs ; }On kuitenkin huomattava, että alla olevassa yleisemmässä AddingFunc-toteutuksessa uusi syntaksi ei hyödy lyhyydestä:
malli < typename LHS , typename RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: eteenpäin < LHS > ( lhs ) + std :: eteenpäin < RHS > ( rhs )) { paluu std :: eteenpäin < LHS > ( lhs ) + std :: eteenpäin < RHS > ( rhs ); } malli < typename LHS , typename RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // sama vaikutus kuin std::eteenpäin edellä { paluu std :: eteenpäin < LHS > ( lhs ) + std :: eteenpäin < RHS > ( rhs ); } malli < typename LHS , typename RHS > decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // sama vaikutus kuin kirjoittamalla tyyppi loppuun AddingFunc ( LHS && lhs , RHS && rhs ) { paluu std :: eteenpäin < LHS > ( lhs ) + std :: eteenpäin < RHS > ( rhs ); }Uutta syntaksia voidaan käyttää yksinkertaisemmissa ilmoituksissa ja ilmoituksissa:
rakentaa SomeStruct { auto FuncName ( int x , int y ) -> int ; }; auto SomeStruct :: FuncName ( int x , int y ) -> int { paluu x + y _ }Avainsanan " " autokäyttö tarkoittaa tässä tapauksessa vain myöhäistä ilmoitusta palautustyypistä, eikä se liity sen automaattiseen päättelyyn.
Objektien rakentajien parantaminenStandardi C++ ei salli yhden luokan konstruktoria kutsua toisesta saman luokan konstruktorista; Jokaisen rakentajan on alustattava täysin kaikki luokan jäsenet tai kutsuttava luokan menetelmiä tehdäkseen niin. Luokan ei-vakiojäseniä ei voida alustaa paikassa, jossa kyseiset jäsenet on ilmoitettu.
C++11 pääsee eroon näistä ongelmista.
Uusi standardi sallii yhden luokan konstruktorin kutsumisen toisesta (ns. delegaatio). Tämän avulla voit kirjoittaa rakentajia, jotka käyttävät muiden rakentajien käyttäytymistä ilman päällekkäistä koodia.
Esimerkki:
class SomeType { int numero ; julkinen : SomeType ( int uusi_numero ) : numero ( uusi_numero ) {} SomeType ( ) : SomeType ( 42 ) {} };Esimerkistä voit nähdä, että rakentaja SomeTypeilman argumentteja kutsuu saman luokan konstruktoria kokonaislukuargumentilla alustaakseen muuttujan number. Samanlainen vaikutus voitaisiin saavuttaa määrittämällä tälle muuttujalle sen ilmoituksessa alkuarvo 42.
class SomeType { int numero = 42 ; julkinen : SomeType () {} eksplisiittinen SomeType ( int uusi_numero ) : numero ( uusi_numero ) {} };Mikä tahansa luokan rakentaja alustaa numberarvon 42, jos se ei itse anna sille eri arvoa.
Java , C# ja D ovat esimerkkejä kielistä, jotka myös ratkaisevat nämä ongelmat .
On huomattava, että jos C++03:ssa objektin katsotaan olevan täysin luotu, kun sen konstruktori on suorittanut suorituksen, niin C++11:ssä, kun vähintään yksi delegoiva konstruktori on suoritettu, loput konstruktorit työskentelevät täysin rakennettu esine. Tästä huolimatta johdetun luokan objektit rakennetaan vasta, kun kaikki perusluokkien konstruktorit on suoritettu.
Virtuaalitoimintojen eksplisiittinen korvaaminen ja lopullisuusOn mahdollista, että virtuaalisen menetelmän allekirjoitus on muutettu perusluokassa tai asetettu väärin johdettuun luokassa alun perin. Tällaisissa tapauksissa annettu menetelmä johdetussa luokassa ei ohita vastaavaa menetelmää perusluokassa. Joten jos ohjelmoija ei muuta menetelmän allekirjoitusta oikein kaikissa johdetuissa luokissa, menetelmää ei ehkä kutsuta oikein ohjelman suorittamisen aikana. Esimerkiksi:
struct Base { virtuaalinen void some_func (); }; struct Johdettu : Base { void sone_func (); };Tässä johdetussa luokassa ilmoitettu virtuaalisen funktion nimi on kirjoitettu väärin, joten tällainen funktio ei ohita Base::some_func, eikä sitä siksi kutsuta polymorfisesti osoittimen tai viittauksen kautta perusaliobjektiin.
C++11 lisää mahdollisuuden seurata näitä ongelmia käännösaikana (ajon aikana). Tämä ominaisuus on valinnainen taaksepäin yhteensopivuuden vuoksi. Uusi syntaksi näkyy alla:
rakenne B { virtuaalinen void some_func (); virtuaalinen void f ( int ); virtuaalinen void g () const ; }; rakenne D1 : julkinen B { void sone_func () ohitus ; // virhe: virheellinen funktion nimi void f ( int ) override ; // OK: ohittaa saman funktion perusluokassa virtual void f ( long ) override ; // virhe: parametrityyppi ei täsmää virtual void f ( int ) const override ; // error: function cv-qualification mismatch virtual int f ( int ) override ; // virhe: paluutyyppi ei täsmää virtual void g () const final ; // OK: ohittaa saman funktion perusluokassa virtual void g ( long ); // OK: uusi virtuaalifunktio }; rakenne D2 : D1 { virtuaalinen void g () const ; // virhe: yritä korvata lopullinen funktio };Virtuaalifunktion määritteen olemassaolo finaltarkoittaa, että sen korvaaminen on mahdotonta. Myöskään lopullisella määritteellä määritettyä luokkaa ei voi käyttää perusluokkana:
struct F lopullinen { int x , y ; }; struct D : F // virhe: periytyminen lopullisista luokista ei sallittu { int z ; };Tunnisteilla overrideja finalon erityinen merkitys vain, kun niitä käytetään tietyissä tilanteissa. Muissa tapauksissa niitä voidaan käyttää normaaleina tunnisteina (esimerkiksi muuttujan tai funktion nimenä).
Nollaosoittimen vakioC:n ilmestymisestä vuonna 1972 lähtien vakiolla 0 on ollut kokonaisluvun ja nollaosoittimen kaksoisrooli. Yksi tapa käsitellä tätä C-kielelle ominaista monitulkintaisuutta on makro NULL, joka tyypillisesti suorittaa ((void*)0)tai korvauksen 0. C++ eroaa C:stä tässä suhteessa sallien vain 0nollaosoittimen käytön vakiona. Tämä johtaa huonoon vuorovaikutukseen toimintojen ylikuormituksen kanssa:
void foo ( char * ); void foo ( int );Jos makro NULLon määritelty 0(joka on yleistä C++:ssa), rivi foo(NULL);johtaa kutsuun foo(int), ei foo(char *)niin kuin pikakatselu koodiin saattaisi ehdottaa, mikä ei läheskään varmasti ole ohjelmoijan tarkoitus.
Yksi C++11:n uutuuksista on uusi avainsana kuvaamaan nollaosoittimen vakiota - nullptr. Tämä vakio on tyyppiä std::nullptr_t, joka voidaan implisiittisesti muuntaa minkä tahansa osoittimen tyypiksi ja verrata mihin tahansa osoittimeen. Implisiittistä muuntamista integraalityypiksi ei sallita, paitsi bool. Alkuperäinen standardiehdotus ei sallinut implisiittistä muuntamista loogisiksi, mutta standardiluonnosryhmä salli tällaiset muunnokset yhteensopivuuden vuoksi tavanomaisten osoitintyyppien kanssa. Ehdotettua sanamuotoa muutettiin yksimielisen äänestyksen jälkeen kesäkuussa 2008 [1] .
Taaksepäin yhteensopivuuden vuoksi vakiota 0voidaan käyttää myös nollaosoittimena.
char * pc = nullptr ; // true int * pi = nullptr ; // true bool b = nullptr ; // oikein. b = false. int i = nullptr ; // virhe foo ( nullptr ); // kutsuu foo(char *), ei foo(int);Usein rakenteet, joissa osoitin on taatusti tyhjä, ovat yksinkertaisempia ja turvallisempia kuin muut - joten voit ylikuormittaa . nullptr_t
luokka Hyötykuorma ; class SmartPtr { SmartPtr () = oletus ; SmartPtr ( nullptr_t ) {} // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< eksplisiittinen SmartPtr ( hyötykuorma * aData ) : fData ( aData ) {} // kopioi rakentajat ja op= jättää pois ~ SmartPtr () { delete fData ; } yksityinen : Hyötykuorma * fData = nullptr ; } SmartPtr getPayload1 () { return nullptr ; } // SmartPtr(nullptr_t) overload kutsutaan. Voimakkaasti kirjoitetut enumitNormaalissa C++:ssa enumit eivät ole tyyppiturvallisia. Itse asiassa niitä edustavat kokonaisluvut huolimatta siitä, että itse luettelointityypit eroavat toisistaan. Tämä mahdollistaa kahden arvon vertailun eri enumeista. Ainoa vaihtoehto, jonka C++03 tarjoaa enumeiden suojaamiseen, on olla implisiittisesti muuntamatta yhden enumin kokonaislukuja tai elementtejä toisen enum-elementeiksi. Myös tapa, jolla se esitetään muistissa (kokonaislukutyyppi), on toteutuksesta riippuvainen eikä siksi ole kannettava. Lopuksi luetteloelementeillä on yhteinen laajuus, mikä tekee mahdottomaksi luoda samannimisiä elementtejä eri luetteloissa.
C++11 tarjoaa näille enumeille erityisen luokituksen, joka ei sisällä yllä olevia haittoja. Tällaisten luetteloiden kuvaamiseen käytetään ilmoitusta enum class(se voidaan käyttää enum structmyös synonyyminä):
enum luokkaluettelo { _ Val1 , Val2 , Val3 = 100 , Val4 , /* = 101 */ };Tällainen luettelo on tyyppiturvallinen. Luokan enum-elementtejä ei voida implisiittisesti muuntaa kokonaisluvuiksi. Tämän seurauksena myös vertailu kokonaislukuihin on mahdotonta (lauseke Enumeration::Val4 == 101johtaa käännösvirheeseen).
Luokkaluettelotyyppi on nyt toteutuksesta riippumaton. Oletuksena, kuten yllä olevassa tapauksessa, tämä tyyppi on int, mutta muissa tapauksissa tyyppi voidaan asettaa manuaalisesti seuraavasti:
enum luokka Enum2 : unsigned int { Val1 , Val2 };Enumin jäsenten laajuus määräytyy enumin nimen laajuuden mukaan. Elementtinimien käyttäminen edellyttää luokkaluettelon nimen määrittämistä. Joten esimerkiksi arvo Enum2::Val1on määritelty, mutta arvoa Val1 ei ole määritelty.
Lisäksi C++11 tarjoaa mahdollisuuden määritellä selkeästi laajuuden ja taustalla olevat tyypit tavallisille enumeille:
enum Enum3 : etumerkitön pitkä { Val1 = 1 , Val2 };Tässä esimerkissä enum-elementtien nimet on määritetty enum-avaruudessa (Enum3::Val1), mutta taaksepäin yhteensopivuuden vuoksi elementtien nimet ovat saatavilla myös yhteisessä laajuudessa.
Myös C++11:ssä on mahdollista ennakkoilmoittaa enumeita. Aiemmissa C++-versioissa tämä ei ollut mahdollista, koska enumin koko riippui sen elementeistä. Tällaisia ilmoituksia voidaan käyttää vain, kun luettelon koko on määritetty (eksplisiittisesti tai implisiittisesti):
enum Enum1 ; // ei kelpaa C++:lle ja C++11:lle; taustalla olevaa tyyppiä ei voida määrittää enum Enum2 : unsigned int ; // tosi C++11:lle, jonka taustalla oleva tyyppi on nimenomaisesti määritelty enum- luokka Enum3 ; // totta C++11:lle, taustalla oleva tyyppi on int enum- luokka Enum4 : unsigned int ; // totta C++11:lle. enum Enum2 : allekirjoittamaton lyhyt ; // ei kelpaa C++11:lle, koska Enum2 oli aiemmin ilmoitettu eri taustalla KulmasulutTavalliset C++-jäsentimet määrittelevät aina ">>"-merkkiyhdistelmän oikealle siirtooperaattoriksi. Välilyönnin puuttuminen sulkevien kulmasulkujen välillä malliparametreissa (jos ne ovat sisäkkäisiä) käsitellään syntaksivirheenä.
C++11 parantaa jäsentimen toimintaa tässä tapauksessa siten, että useat suorakulmasulut tulkitaan sulkeviksi malliargumenttiluetteloiksi.
Kuvattu käyttäytyminen voidaan korjata vanhan lähestymistavan hyväksi sulkujen avulla.
malli < luokka T > luokka Y { /* ... */ }; Y < X < 1 >> x3 ; // Oikein, sama kuin "Y<X<1> > x3;". Y < X < 6 >> 1 >> x4 ; // Syntaksivirhe. Sinun on kirjoitettava "Y<X<(6>>1)>> x4;".Kuten yllä näkyy, tämä muutos ei ole täysin yhteensopiva edellisen standardin kanssa.
Eksplisiittiset muunnosoperaattoritC++-standardi tarjoaa avainsanan explicitmuuntajana yksiparametrisille rakentajille, jotta tällaiset konstruktorit eivät toimi implisiittisinä muunnoskonstruktoreina. Tämä ei kuitenkaan vaikuta varsinaisiin muunnosoperaattoreihin millään tavalla. Esimerkiksi älykäs osoitinluokka voi sisältää operator bool()normaalin osoittimen jäljittelemiseksi. Tällaista operaattoria voidaan kutsua esimerkiksi näin: if(smart_ptr_variable)(haara suoritetaan, jos osoitin ei ole nolla). Ongelmana on, että tällainen operaattori ei suojaa muilta odottamattomilta muunnoksilta. Koska tyyppi boolon ilmoitettu aritmeettiseksi tyypiksi C++:ssa, implisiittinen muunnos mihin tahansa kokonaislukutyyppiin tai jopa liukulukutyyppiin on mahdollista, mikä puolestaan voi johtaa odottamattomiin matemaattisiin operaatioihin.
C++11:ssä avainsana explicitkoskee myös muunnosoperaattoreita. Kuten rakentajat, se suojaa odottamattomilta implisiittisiltä muunnoksilta. Tilanteet, joissa kieli kontekstuaalisesti odottaa loogista tyyppiä (esimerkiksi ehdollisissa lausekkeissa, silmukoissa ja loogisissa operaattorioperandeissa), katsotaan kuitenkin eksplisiittisiksi muunnoksiksi, ja eksplisiittinen bool-muunnosoperaattori kutsutaan suoraan.