C++11
C++11-ը C++ ծրագրավորման լեզվի ստանդարտի տարբերակ է։ Այն Միջազգային ստանդարտացման կազմակերպության (ISO) կողմից հաստատվել է 2011 թվականի օգոստոսի 12-ին՝ փոխարինելով C++03[1] ստանդարտը և նախորդելով 2014 թ․ օգոստոսի 18-ին հաստատված C++14 ստանդարտին[2]։ Այս ստանդարտների անունները կառուցված են անվան մեջ ստանդարտացման տարեթիվը նշելու սկզբունքով, չնայած այն ավելի վաղ հայտնի էր C++0x անունով, քանի որ դրա հրապարակումը ակնկալվում էր մինչև 2010 թվականը[3]։
Տեսակ | ISO standard edition? |
---|---|
Ենթադաս | C++ |
Կատարման ձև | կոմպիլյացիա |
Առաջացել է | օգոստոսի 12, 2011 |
Ստեղծող | Բյերն Ստրաուստրուպ |
Տիպիզացիա | ստատիկ, խիստ |
Հիմնական իրականացումներ | g++, clang++ |
Հիմքի վրա է | C++03 |
Ներշնչվել է | C, Simula |
Ներշնչել է | Java |
Նախորդ | C++03 |
Հաջորդ | C++14 |
Անվանված է | 2011 |
Կայք | iso.org/standard/50372.html(անգլ.) |
Չնայած այն բանին, որ նախագծման նպատակներից էր լեզվի միջուկում փոփոխություններ կատարելու փոխարեն նախապատվությունը տալ գրադարաններում կատարվող փոփոխություններին, C++11 տարբերակում որոշ փոփոխություններ են կատարվել նաև լեզվի միջուկում։ Լեզվի միջուկում զգալի փոփոխություններ են կրել բազմահոսք ծրագրավորման, ընդհանրացված ծրագրավորման, միաձև արժեքավորման հնարավորությունները, ինչպես նաև արտադրողականությունը[4]։ Զգալի փոփոխություններ են կատարվել նաև C++ լեզվի ստանդարտ գրադարանում (STL)[5]։
Ստանդարտի առաջարկվող փոփոխությունները
խմբագրելԻնչպես արդեն նշվել է, փոփոխությունները վերաբերում են ինչպես C++-ի միջուկին, այնպես էլ ստանդարտ գրադարանին։
Ապագա ստանդարտի ամեն հատվածը մշակելիս հանձնաժողովը օգտագործել է մի քանի սկզբունքներ.
- Լեզվի կայունության աջակցում և համատեղելիության ապահովումը C++98 ստանդարտի հետ, հնարավորության դեպքում նաև C-ի հետ
- Նոր հնարավորությունները ավելացնել ստանդարտ գրադարանի ` ոչ թե միջուկի միջոցով
- Նախընտրել այն փոփոխությունները, որոնք լավացնում են ծրագրավորելու հմտությունները(տեխնիկան)
- Կատարելագործել C++-ը համակարգի և գրադարանային դիզայնի տեսանկյունից, նոր հանրավորություններ, օգտակար և առանձին ծրագրերի փոխարեն
լավացնել տիպերի ահահովությունը և ապահովել այլընտրանքային ապահովություն վտանգավոր մոտեցումների ժամանակ
- Բարձրացնել արտադրողականությունը և անմիջապես սարքավորման հետ աշխատելու հնարավորությունը, ապահովել իրական՝ շատ տարածված խնդիրների լուծումը.
իրագործել «Չես վճարում նրա համար ինչ չես օգտագործում» սկզբունքը
- Դարձնել C++-ը հեշտ սովորելու համար, առանց ծրագրավորողների հնարավորությունները քչացնելու
Ուշադրություն է դարձվել սկսնակներին, ովքեր ծրագրավորողների մեծ մասն են կազմում։ Շատ սկսնակներ չեն ձգտում խորացնել C++-ի գիտելիքները, սահմանափակվելով օգտագործումը նեղ մասնագիտական խնդիրներ լուծելով։ Բացի այդ հաշվի առնել C++-ի ունիվերսալությունը և օգտագործման բազմազանությունը, նույնիսկ հմուտ ծրագրավորողը կարող է հայտնվել սկսնակի դերում նոր ծրագրավորման մեթոդոլոգիա օգտագործելիս։
Լեզվի միջուկի ընդլայնումը
խմբագրելՀանձնաժողովի առաջնային խնդիրն էր զարգացնել լեզվի միջուկը։ Միջուկը զգալիորեն բարելավել է, ավելացվել են բազմահոսքայնության և ընդհանուր ծրագրավորման հնարավորությունները։
Հարմարության համար միջուկի հնարավորությունները և նրա փոփոխությունները բաժանվել են երեք մասի. արտադրողականության բարձրացում, հարմարության լավացում և նոր հնարավորություններ։
Հարմարության համար
խմբագրելԼեզվի այս բաղադրիչը ներմուծված է հիշողության ծախսի քչացման և արտադրողականության մեծացման համար։
Ժամանակավոր օբեկտների հղումներ և տեղափոխման սեմանտիկա
խմբագրելC++ ի ստանդարտով ժամանակավոր օբեկտը, որը առաջացել է արտահայտության հաշվարկի ժամանակ, կարելի է փոխանցել ֆունկցիային, սակայն միայն հաստատուն(const
) հղումով։ Ֆունկցիան ի վիճակի չէ որոշել, կարելի է արդյոք դիտարկել փոխանցված օբեկտը ժամանակավոր և փոփոխոթյունների ենթակա (հաստատուն(const) օբեկտը որը նույնպես կարելի է փոխանցել այդ հղումով, չի կարելի փոփոխվել (օրինական))։ Սա խնդիր չէ պարզ կառուցվածքների համար՝ ինչպիսին complex֊ն է, սակայն բարդ կառուցվածք ունեցող տիպերի համար, որոնց անհրաժեշտ է հիշողության հատկացում-ազատում, ժամանակավոր օբեկտների ոչնչացումը և հիմնականի ստեղծումը երկարատև է, այն դեպքում երբ կարելի է ուղղակի անմիջապես փոխանցել ցուցիչը։
C++ 11 ում հայտնվել է հղման նոր տեսակ՝ rvalue-հղում (անգլ․ rvalue reference)։ Այն հայտարարվում է հետևյալ կերպ՝ type &&
։ Վերբեռնման նոր օրենքները թույլ են տալիս օգտագործել տարբեր վերբեռնման ֆուկցիաներ ոչ հաստատուն ժամանակավոր օբյեկտներով, որոնք հայտարարված են rvalue տիպի, և այլ մնացած բոլոր օբեկտների համար։ Նշված նորամուծությունը թույլ է տալիս իրականացնել տեղափոխման սեմանտիկան (Move semantics)։
Օրինակ std::vector
-ը հասարակ կազմ է C զանգվածի և փոփոխականի, որը պահպանում է իր չափը։ Ընդօրինակող կոնստրուկտորը (copy constructor
) std::vector::vector(const vector &x)
կստեղծի նոր զանգված և կկրկնօրինակի տվյալները։
Տեղափոխման կոնստրուկտորը (move constructor
) std::vector::vector(vector &&x)
կարող է ուղղակի փոխանակվել ցուցիչներով և երկարություն պարունակող փոփոխականներով։ Հայտարարման օրինակ է՝
template class vector {
vector (const vector &); // պատճենող կոնստրուկտոր (դանդաղ)
vector (vector &&); // ժամանակավոր օբյեկտից տեղափոխող կոնստրուկտոր (արագ)
vector & operator = (const vector &); // սովորական վերագրում (դանդաղ)
vector & operator = (vector &&); // ժամանակավոր օբյեկտի տեղափոխում (արագ)
};
Ընդհանրացված հաստատուն արտահայտություններ
խմբագրելC++ ում միշտ էլ եղել է հաստատուն արտահայտություն հասկացությունը։ Այսպիսով, 3+4 տեսքի արտահայտությունները միշտ վերադարձրել են նույն արդյունքը, առանց կողմնակի էֆեկտների։ Իրենք իրենցով հաստատուն արտահայտությունները կոմպիլյատորին տալիս են հնարավորություն հարմարավետ օպտիմիզացիաների։
Կոմպիլյատորները հաշվում են նման արտահայտությունների արդյունքը միայն կոմպիլիացիայի փուլում են պահում այն ծրագրում։ Այսպիսով, նմանատիպ արտահայտությունները հաշվում են մեկ անգամ։ Նաև կան դեպքեր, երբ լեզվի ստանդարտը պահանջում է հաստատուն արտահայտությունների օգտագործումը։
Այդպիսի օրինակ է արտաքին զանգվածի տարբերակումը կամ թվարկումները (enum
)
int GiveFive() {return 5;}
int some_value[GiveFive() + 7]; // 12 չափանի զանգվածի ստեղծում; արգելված է C++ -ում
Վերոհիշյալ կոդը արգելված է C++ ում, քանի որ GiveFive() + 7
պաշտոնապես կոմպիլացիայի փուլում չի հանդիսանում հաստատուն արտահայտություն։ կոմպիլատորին այդ պահին ուղակի հայտնի չէ, որ ֆունկցիան իրականում վերադարձնում է հաստատուն։Կոմպիլատորի անհանգստության պատճառ հանդիսանում է այն, որ ֆունկցիան կարող է ազդել գլոբալ փոփոխականի վրա, աշխատանքի ընթացքում կանչել այլ ոչ հաստատուն ֆունկցիա և այլն։
C++11 ներմուծել է բանալի բառ (keyword) constexpr
, որը թույլատրում է օգտագործողին երաշխավորել, որ կամ ֆունկցիան կամ օբեկտի Կոնստրուկտորը վերադարձնում է հաստատուն կոմպիլիացիայի ժամանակ։ Վերևի կոդը կարելի է գրել հետևյալ կերպ՝
constexpr int GiveFive()
{
return 5;
}
int some_value[GiveFive() + 7]; // 12 չափանի զանգվածի ստեղծում; թույլատրված է C++11 -ում
Այդպիսի բանալի բառը (Անգլերեն. keyword) թույլ է տալիս կոմպիլիատորին հասկանալ և համոզվել, որ GiveFive
վերադարձնում է հաստատուն։ constexpr
ի օգտագործումը առաջացնում է ֆունկցիայի աշխատանքի խիստ սահմանափակումներ.
1. այդպիսի ֆունկցիան պետք է վերադարձնի արժեք
2. ֆունկցիայի մարմինը պետք է երևա վերադարձվող արտահայտությունում
3. արտահայտությունը պետք է բաղկացած լինի հաստատուններից և կամ այլ constexpr
ֆունկցիաների կանչերից
4. constexpr
նշված ֆունկցիան չի կարող օգտագործվել մինչև տվյալ մասի կոմպիլացվելը
Ստանդարտի նախորդ տսրբերակում հաստատուն արտահայտություններում կարող էին օգտագործվել միայն ամբողջ տիպի կամ համարակալման (enum
) տիպի փոփոխականներ։C++ 11 ում այդ սահմանափակումը հանված է այն փոփոխականների համար, որոնց հայտարարումից առաջ դրված է constexpr
բանալի բառը (keyword)։
constexpr double accelerationOfGravity = 9.8;
constexpr double moonGravity = accelerationOfGravity / 6;
Այդպիսի փոփոխականները արդեն անուղղակի համարվում են նշված const
բանալի բառով։ Նրանցում կարող են պարունակվել միայն հաստատուն արտահայտությունների արդյունքներ կամ դրանց կոնստրուկտորներ։
Օգտագործողի կողմից նշված տիպից հաստատուն արժեքի նախագծման անհրաժեշտության դեպքում, այդպիսի տիպերի կոնստրուկտորները կարող են ներկայացված լինել constexpr
ի
օգնությամբ։ Հաստատուն արտահայտությունների կոնստրուկտորները նման են հաստատուն ֆունկցիաներին, և պետք է կոմպիլացված լինի մինչ կոմպիլիացիայի այդ փուլին հասնելը։ Այդպիսի կոնստրուկտորը պետք է լինի դատարկ, և սկզբնարժեքավորի(initialize
) իր տիպի անդամներին միայն հաստատուններով։
Պարզ տվյալների սահմանման փոփոխություններ
խմբագրելՍտանդարդ C++-ում միայն որոշակի օրենքներն բավարարող կառուցվածքներն(Struct
) են կարող համարվել պարզ տվյալների տիպ(Plain Data Type (POD)
)։Գոյություն ունեն ծանրակշիռ պատճառներ սպասելու այդ օրենքների ընդլայմանը, այն պատճառով, որ ավելի մեծ քանակի տրպեր դիտարկվի պարզ (POD
)։ Այդ օրենքներին բավարարող տիպերը կարող են օգտագործվել օբեկտային շերտի իրականացման մեջ C ի հետ համաձայնեցված(совместимого)։ սակայն C++ 03-ում այս օրենքների ցուցակը չափազանց խիստ է։
C++ 11 ը կթուլացնի մի քանի օրենքներ, որոնք վերաբերվում են պարզ տվյալների
տիպի(POD
) սահմանմանը։
Դասը դիտարկվում է որպես պարզ տվյալների տիպ (POD
), եթե այն ՏՐԻՎԻԱԼ Է (trivial
) ստանդարտ դասավորությամբ (standard-layout
) և եթե նրա բոլոր ոչ ստատիկ անդամ-տվյալները նույնպես հանդիսանում են պարզ տվյալների
տիպի(POD
) :
Տրիվիալ Դաս - այն դասն է որը՝
1. պարունակում է տրիվիալ լռելյան (<>default) կոնստրուկտոր
2. չի պարունակում ոչ տրիվիալ ընդորինակող կոնստրուկտոր
3. չի պարունակում ոչ տրիվիալ տեղափոխման կոնստրուկտոր
4. չի պարունակում ոչ տրիվիալ ընդորինակող վերագրման օպերատոր (copy assignment operator)
5. չի պարունակում ոչ տրիվիալ տեղափոխման վերագրման օպերատոր (move assignment operator)
6. պարունակում է տրիվիալ դեստրուկտոր
Ստանդարտ դասավորությամբ դասը-դա դասն է, որը՝
1. չի պարունակում ոչ ստատիկ անդամ-տվյալներ, որոնք ունեն ունեն ոչ ստանդարտ դասավորությամբ դասի տիպ(կամ այդպիսի տիպի զանգված) կամ հղման տիպ
2. չի պարունակում վիրտուալ ֆունկցիաներ
3. չի պարունակում վիրտուալ հիմքային(base) դաս
4. ունի միևնույն հասանելիության տեսակը (public
, private
, protected
) բոլոր ոչ ստատիկ անդամ-տվյալների համար
5. չի պարունակում ոչ ստանդարտ դասավորությամբ հիմքային դաս
6. չի հանդիսանում դաս, որը միառժամանակ պարուանկում է ժառանգված և չժառանգված ոչ ստատիկ անդամ-տվյալներ, կամ միարժամանակ մի քանի հիմքային դասերից ժառանգված ոչ ստատիկ անդամ-տվյալներ
7. չի պարունակում հիմքային դասեր նույն տիպի ինչ առաջին ոչ ստատիկ անդամ-տվյալներ(եթե այդպիսիք կան)
Կոմպիլյացիայի արագացում
խմբագրելԱրտաքին կաղապարներ
խմբագրելՍտանդարտ C++ ում կոմպիլիատորը պետք է инстанцировать կաղապարը ամեն անգամ, երբ հանդիպում է в единице трансляции его полную специализацию.
Դա կարող է զգալիորեն մեծացնել կոմպիլիացիայի ժամանակը, մանավանդ այն ժամանակ, երբ կաղապարը инстанцирован նույն պարամետրերով տրանսլիացիայի միավորների մեծ թվում։ Ներկայումս չկա ձև C++ ին հայտնելու, որ инстанцирования չպետք է լինի։
C++11 ում ներմուծվել է արտաքին կաղապարների գաղափարը։C++11 ում կա գրելաձև կոմպիլիատորին հասկացնելու, որ կաղապարը պետք է инстанцирован
որոշակի կետում։
template class std::vector;
C++ ում չկա հնարավորություն արգելելու կոմպիլիատորին инстанцировать կաղապարը Տռանսլիացիայի միավորում։ C++11 ը ուղղակի ընդլայնել է այս գրելաձևը(syntax)
extern template class std::vector;
Այս արտահայտությունը ասում է կոմպիլիատորին не инстанцировать տվյալ Տռանսլիացիայի միավորում։
Օգտագործման հարմարավետության բարձրացում
խմբագրելԱյս հնարավորությունները նախատեսված են լեզվի օգտագործումը հեշտացնելու համար։
Դրանք հնարավորություն են տալիս ուժեղացնել տիպային անվտանգությունը, մինիմալացնել կոդի կրկնօրինակումը, դժվարեցնել կոդի սխալ օգտագործումը և այլն։
Սկզբնարժեքավորման ցուցակներ
խմբագրելՍկզբնարժեքավորման ցուցակների հասկացությունը C++ եկել է C ից։ Գաղփարը կայանում է նրանում, որ կառուցվածքը կամ զանգվածը կարող են ստեղծված լինել արժեքների ցուցակի միջոցով, ընդորում կառուցվածքի անդամների հերթականությամբ։
Սկզբնարժեքավորման ցուցակները ռեկուրսիվ են, ինչը թույլ է տալիս օգտագործել դրանք
կառուցվածքի զանգվածի և կառուցվածքի մեջ ներդրված կառուցվածքի ժամանակ։
struct Object
{
float first;
int second;
};
Object scalar = {0.43f, 10}; // մեկ օբյեկտ, որտեղ first=0.43f և second=10
Object anArray[] = {{13.4f, 3}, {43.28f, 29}, {5.934f, 17}}; // երեք տարր պարունակող զանգված
Սկզբնարժեքավորման ցուցակները շատ օգտակար են ստատիկ ցուցակների և այն դեպքերում, երբ պետք է կառուցվածքը սկզբնարժեքավորել կոնկրետ արժեքով։
C++ ը նաև պարունակում է կոնստրուկտորներ, որոնք կարող են պարունակել օբեկտների սկզբնարժեքավորման ընդհանուր մասը։
C++ ի ստանդարտը թույլ է տալիս օգտագործել սկզբնարժեքավորման ցուցակներ
կառուցվածքների և դասերի համար այն պայմանով, որ դրանք համապատասխանում են
պարզ տվյալների տիպի(POD) սահմանմանը։
Դասերը, որորնք չեն հանդիսանում POD, չեն կարող սկզբնավորման համար օգտագործել
սկզբնարժեքավորման ցուցակներ, դա վերբերվում է նաև ստանդարտ C++ի կոնտեյներներն, ինչպիսիք են վեկտորը։
C++11 ը կապել է սկզբնարժեքավորման ցուցակների գաղափարը և կաղապար դասը,
std::initializer_list
անունով։ Դա թույլ է տվել կոնստրուկտորնեին և այլ ֆունկցիաներին ստանալ սկզբնարժեքավորման ցուցակները որպես պարամետրեր։ օրինակ ՝
class SequenceClass
{
public: SequenceClass(std::initializer_list list);
};
Նկարագրման այս ձևը թույլ է տալիս ստեղծել SequenceClass
ամբողջ թվերի հաջորդականությունից հետևյալ կերպ՝ SequenceClass someVar= {1, 4, 5, 6}
;
Այս օրինակում ցույց է տված սկզբնարժեքավորման ցուցակների հատուկ տիպի կոնստրուկտորների աշխատանքը։ Դասերը, որոնք պարունակում են այսպիսի կոնստրուկտորներ, մշակվում են հատուկ ձևով սկզբնարժեքավորման ժամանակ։
std::initializer_list<>
դասը սահմանված է C++ 11 ի ստանդարտ գրադարանում(STL
)։
Սակայն այս դասի օբկտները կարողեն ստեղծվել C++11 ի կոմպիլիատորով միայն ստատիկ, օգտագործելով {} այս տիպի փակագծերը։ Ցուցակը ստեղծումից հետո կարող է կրկնօրինակվել, սակայն այն կլինի կրկնօրինակում հղումով(copy by reference
)։
Սկզբնարժեքավորման ցուցակը հանդիսանում է հաստատուն՝ ոչ անդամները և ոչ էլ տվյալները ստեղծումից հետո փոփոխման ենթակա չեն։
Քանի որ <std::initializer_list<>
ը հանդիսանում է լիարժեք տիպ, այն կարող է օգտագործվել ոչ միայն կոնստրուկտորներում։ Հասարակ ֆունկցիաները կարող են ստանալ սկզբնարժեքավորման ցուցակները որպես արգումենտ(պարամետր)։ Օրինակ՝
void FunctionName(std::initializer_list list);
FunctionName({1.0f, -3.45f, -0.4f});
Ստանդարտ կոնտեյներները կարող են սկզբնարժեքավորվել հետևյալ կերպ.
std::vector v = { "xyzzy", "plugh", "abracadabra" };
std::vector v{ "xyzzy", "plugh", "abracadabra" };
Ունիվերսալ սկզբնարժեքավորում
խմբագրելC++ ի ստանդաարտ կան մի շարք խնդիրներ կապված սկզբնարժեքավորման տեսակների հետ։ Գոյություն ունեն տիպերի սկզբնարժեքավորման մի քանի եղանակներ, և ոչ բոլորն են հանգեցնում նույն արդյունքին։ Օրինակ՝ ավանդական սկզբնարժեքավորման կոնստրուկտորի գրելաձևը կարող է հասկացվել կոմպիլիատորի կողմից որպես ֆունցիայի նկարագրում, ուստի պետք է ձեռնարկել լրացուցիչ միջոցներ, որպեսզի կոմպիլիատորը չսխալվի վերլուծության ժամանակ։ Միայն խմբավորման և (POD
) տիպերը կարող են սկզբնաորվել արգումենտների սկզբնավորուման միջոցով (SomeType var = {/*stuff*/};
)
C++11 ը ներկայացնում է գրելաձև, որը թույլատրում է բոլոր տեսակի օբեկտների միացիալ սկազբնարժեքավորում, սկզբնարժեքավորման ցուցակների գրելաձևի ընդլայման օգնությամբ։
struct BasicStruct
{
int x;
double y;
};
struct AltStruct
{
AltStruct(int x, double y)
: x_(x)
, y_(y)
{}
private:
int x_;
double y_;
};
BasicStruct var1{5, 3.2};
AltStruct var2{2, 4.3};
var1
ի սկզբնավորումը աշխատում է այնպես, ինչպես արգումենտներով սկզբնարժեքավորման ժամանակ, այսինքն, յուրաքանչյուր օբյեկտ սկզբնարժեքավորված կլինի սկզբնարժեքավորման ցուցակի իրեն համապատասխանող արժեքի կրկնօրինակով։ Անհրաժեշտության դեպքում կկիրառվի տիպերի անուղղակի փոխարկում(կոնվերտացիա)։Եթե անհրաժեշտ փոխարկումը հնարավոր չէ, կոդը
կհամարվի սխալ։
var2
ի սկզբնարժեքավորման ժամանակ կկանչվի կոնստրուկտոր։
Տրված է հնարավորություն գրելու նմանատիպ կոդ.
struct IdString
{
std::string name;
int identifier;
};
IdString GetString()
{
return {"SomeName", 4}; // ուշադրություն դարձրեք տիպի ակնհայտ հայտարարման բացակայությանը
};
Ունիվերսալ սկզբնարժեքավորումը ամբողջովին չի փոխարինում կոնստրուկտորով
սկզբնարժեքավորելու գրելաձևին։ Եթե դասում կա կոնստրուկտոր, որը որպես արգումենտ ստանում է սկզբնարժեքավորման ցուցակ(ИмяТипа(initializer_list);), այն կունենա ավելի բարձր առաջնայնություն համեմատած մյուս օբեկտ ստեղծելու միջոցների։ Օրինակ, C++-ում std::vector
պարունակում է, որպես արգումենտ սկզբնարժեքավորման ցուցակ ստացող կոնստրուկտոր
std::vector theVec{4};
Այս կոդը կբերի որպես արգումենտ սկզբնարժեքավորման ցուցակ ստացող կոնստրուկտորի կանչի, այլ ոչ թե մի պարամետրով կոնստրուկտորի, որը ստեղծում է կոնտեների տրված չափով։ Այդ կոնստրուկտորի կանչի համար օգտագործողը պետք է օգտագործի կոնստրուկտորի կանչի ստանդարտ գրելաձև։
Տիպերի դուրսբերումը
խմբագրելՍտանդարտ C++ փոփոխականը պետք է բացահայտ հայտարարված լինի։ Սակայն կաղապար տիպերի և դրանցով մետածրագրավորման (metaprogramming) ի հայտ գալով, որոշ արտահայտությունների տիպերը, ֆունկցիաների վերադարձվող արժեքները հնարավոր չէ հեշտությամբ նշել։ Դա հանգեցնում է դժվարությունների, փոփոխականների միջանկյալ տվյալները պահելուց։ Երբեմն կարող է անհրաժեշտ լինել իմանալ մետածրագրավորման գրադարանի ներքին միջոցները։
C++11-ը առաջարկում է երկու տարբերակ թեթևացնելու այս խնդիրը։ Առաջին՝ ակնհայտ սկզբնարժեքավորվող փոփոխականը կարող է պարունակել auto
բանալի բառը։ Դա կբերի նրան, որ փոփոխականը կստեղծվի սկզբնարժեքավորվող արժեքի տիպի։
auto someStrangeCallableType = std::bind(&SomeFunction, _2, _1, someObject);
auto otherVariable = 5;
someStrangeCallableType-ը
կլինի այն տիպի, ինչ տիպ կվերադարձնիstd::bind
ֆունկցիան տրված արգումենտների համար։ Այդ տիպը հեշտությամբ դուրս կբերվի կոմպիլիատորի կողմից սեմանտիկ վերլուծության ընթացքում, ինչը ծրագրավորողին կտրվեր մի շարք բարդ հետազոտություններից հետո։
otherVariable
տիպը նույնպես հեշտությամբ սահմանվել, սակայն այն նույն հեշտությամբ կարող էր սահմանվել ծրագրավորողի կողմից։ Դա int տիպն է, այնպիսին ինչչպիսին ամբողջ հաստատունինը։
Բացի դրանից, կոմպիլիացիայի ժամանակ արտահայտությունների տիպերի որոշման համար օգտագործվում է decltype բանալի բառը։ Օրինակ՝
int someInt;
decltype(someInt) otherIntegerVariable = 5;
decltype ը շատ ավելի արյունավետ է օգտագործել auto
ի հետ, որովհետև auto
հայտարարված տիպը հայտնի է միայն կոմպիլիատորին։ Բացի դրանից, decltype ի օգտագործումը կարող է շատ օգտակար լինել արտահայտություններում, որտեղ օգտագործվում են օպերատորների գերբեռնում և կաղապարների սպեցիալիզացիա։
auto
-ն կարող է օգտագործվել նաև կոդի կոկիկության համար, օրնակ.
for (vector::const_iterator itr = myvec.cbegin(); itr != myvec.cend(); ++itr)
փոխարեն կարելի է գրել
for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)
Տարբերույունը ակնհայտ է դառնում, երբ ծրագրավորողը օգտագործում է մեծ թվով կոնտոյներներ, չնայած այն բանին, որ այժմ կա ավելորդ կոդի փոքրացման միջոց՝typedefի օգտագործում։
decltype նշված տիպը կարող է տարբերվել auto
ով դուրս բերված տիպից
#include <vector>
int main()
{
const std::vector v(1);
auto a = v[0]; // a֊-ի տիպն int է
decltype(v[0]) b = 1; // b-֊ի տիպը const int& է (std::vector::operator[](size_type) const մեթոդի արժեքի տիպը)
auto c = 0; // c֊-ի տիպն int է
auto d = c; // d֊-ի տիպն int է
decltype(c) e; // e-֊ի տիպն int է՝ c֊-ի տիպը
decltype((c)) f = c; // f-ի տիպը int& է, քանի որ (c)-֊ն lvalue է
decltype(0) g; // g֊-ի տիպը int է, քանի որ 0֊-ն rvalue է
}
For - հավաքածուի ցիկլ
խմբագրելՍտանդարտ C++-ում տարրերի հավաքածուն վերցնելու համար անհրաժեշտ է բավականին ծավալուն կոդ։ Շատ լեզուներում, օրինակ C#-ում, գոյություն ունի միջոց «foreach», որը ավտոմատ կերպով վերցնում է հավաքածուի տարրերը սկզբից մինչև վերջ։ C++-ը տրամադրում է այդպիսի միջոց։ for
-ի սահմանումը թույլատրում է ավելի հեշտ վերցնել հավաքածուի տարրերը.
int my_array[5] = {1, 2, 3, 4, 5};
for(int &x : my_array)
{
x *= 2;
}
Այս տիպի for
ցիկլը անգլերեն կոչվում է «range-based for», այն կայցելի հավաքածուի յուրաքանչյուր էլեմենտ։ Այն կիրառելի կլինի C լեզվի զանգվածների, ինիցիալիզատորների ցուցակների և այն բոլոր տիպերի համար, որոնց համար սահմանված են begin()
և end()
ֆունկցիաները, որոնք վերադարձնում են իտերատորներ։ Ստանդարտ գրադարանի բոլոր կոնտեյներները, որոնք ունեն begin/end
զույգը կաշխատեն for
ցիկլի հետ հավաքածույով։ Այդպիսի ցիկլը կաշխատի C լեզվի զանգվածների հետ նույնպես, քանի որ C++11 ստանդարտը արհեստականորեն դրանց համար սահմանում է անհրաժեշտ տիպերը (begin
, end
և այլն )։
Լյամբդա-ֆունկցիաներ և արտահայտություններ
խմբագրելՍտանդարտ C++ լեզվում գրադարանային ալգորիթմներից օգտվելիս հաճախ կարիք է առաջանում սահմանել ֆունկցիա-պրեդիկատ հենց այնտեղ, որտեղ որ կանչվում է ալգորիթմը։ Լեզվում գոյություն ունեցող միակ մեխանիզմը ֆունկտոր դաս հայտարարելն էր (ֆունկցիայի ներսում սահմանված դասի օբյեկտ փոխանցելը ալգորիթմը արգելում է (Meyers, Effective STL))։ Իրականում այս տարբերակը համարվում է ժամանակատար և բարդացնում է կոդի ընթերցանելիությունը։ Բացի այդ, C++-ի կանոններն արգելում են դրանց օգտագործումը կաղապարներում՝ դրանով իսկ անհնար դարձնում դրանց նպատակային օգտագործումը։ Ակնհայտ լուծում է հանդիսանում C++11 ստանդարտի լյամբդա-ֆունկցիաների և արտահայտությունների կիրառումը։ Լյամբդա-ֆունկցիաները հայտարարվում են հետևյալ կերպ՝
[](int x, int y) { return x + y; }
Այս անանուն ֆունկցիայի համար վերադարձվող արժեքի տիպը որոշվում է որպես decltype(x+y)
։ Վերադարձվող տիպը կարելի է բաց թողնել այն ներկայացված է որպես return expression
: Դա սահմանափակում է լյամբդա-արտահայտությանը մինչև մեկ արտահայտություն։
Վերադարձվող տիպը կարելի է նաև բացահայտ նշել։
[](int x, int y) -> int { int z = x + y; return z; }
Այս դեպքում ստեղծվում է ժամանակվոր z
փոփոխական վերադարձվելիք արժեքը հիշելու համար։ Ինչպես և սովորական ֆունկցիաներում, այդ ժամանակավոր փոփոխական չի պահվում կանչերից հետո։
Վերադարձվող տիպը լրիվ բաց է թողնվում, եթե ֆունկցիան արժեք չի վերադարձնում(այսինքն void
)։ Հնարավոր է օգտագործել նաև հղումներ փոփոխականների վրա, եթե դրանք հայտարարված են նույն տեսանելիության տիրույթում ինչ լյամբդա-ֆունկցիան։
std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&total](int x) {
total += x;
});
std::cout << total
Սա ցույց կտա ցուցակի բոլոր էլեմենտների գումարը։ Քանի որ total
փոփոխականը հղվում է ստեկի մեջ գտնվող այլ փոփոխականի վրա այդ իսկ պատճառով այն կարող է փոփոխել դրա արժեքը։
Փոփոխականները կարող են փոխանցվել ֆունկցիային առանց &
նշանի, սակայն այդ դեպքում դրանց արժեք ֆունկցիան կկրկնորինակի։
Լյամբդա ֆունկցիաները կարող են օգտագործել իրենց տեսանելիության տիրույթում գտնվող փոփոխականները առանց ականհայտ հղումների։
std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&](int x) {
total += x;
});
Ներքին ռեալիզացիաները կարող են տարբերվել, բայց ենթադրվում է, որ լյամբդա ֆունկցիան կպահպանի ցուցիչը ֆունկցիյա ստեկի վրա, այլ ոչ թե կաշխատի ստեկի առանձին փոփխականների հղումների հետ։
Եթե [&]
փոխարեն օգտագործվի [=]
, ապա բոլոր փոփոխականները կկրկնորինակվեն և լյամբդա ֆունկցիան հնարավոր կլինի օգտագործել անկախ ընթացիկ փոփոխականների հայտարարման տիրույթից։
Լռելիությամբ տվյալները փոխանցելը կարելի է լրացնել առանձին փոփոխականների ցուցակներով։ Եթե անհրաժեշտ է բոլոր արգումենտները փոխանցել հղումով իսկ մեկը ոչ՝ կարելի է վարվել հետևյալ կերպ.
int total = 0;
int value = 5;
[&, value](int x) { total += (x * value); } (1);
Այս դեպքում total
կփոխանցվի հղումով իսկ value
արժեքով։
Եթե լյամբդա ֆունկցիան սահմանված է դասի մեթոդի մեջ ապա այն այդ դասի համար ընկեր (անգլ. friend) ֆունկցիա է։ Այդպիսի ֆունկցիաները կարող են դիմել դասի ներքին անդամներին և օգտագործել դասի տիպի հղումներ։
[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); }
Սա կաշխատի միայն այն դեպքում լյամդա ֆունկցիան ստեղծվել է SomeType
դասի մեթոդում։
Հատուկ ձևով սահմանված է this
ցուցիչի հետ աշխատանքը։ Այն պետք է բացահայտ նշվի լյամբդա ֆունկցիայում։
[this]() { this->SomePrivateMemberFunction(); }
[&]
կամ [=]
օգտագործումը ինքնստինքյան this
ցուցիչը դարձնում է հասանելի։
Լյամբդա ֆունկցիայի տիպը կախված է դրա ռեալիզացիայից, այդ տիպի անունը հայտնի է միայն կոմպիլիատորին։ Եթե անհրաժեշտություն կա լյամբդա ֆունկցիան փոխանցել որպես արգումնետ, ապա այն պետք է լինի կաղապար տիպի կամ պահպանված լինի օգտագործելով std::function
-ը։ Օգտագործելով auto
բանալի բառը (անգլ. keyword) կարող ենք հիշել լյամբդա ֆունկցիան։
auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };
Ֆունկցիաների գրելաձևի այլընտրանքային մոտեցում
խմբագրելԵրբեմն անհրաժեշտություն է առաջանում իրականացնել կաղապար ֆունկցիա, որի վերադարձվող արժեքը պետք է լինի որևէ արտահայտության տիպի։
template <typename LHS, typename RHS>
RETURN_TYPE AddingFunc(const LHS &lhs, const RHS &rhs) // ինչպիսի՞ն պետք է լինի վերադարձվող տիպը
{
return lhs + rhs;
}
Որպեսզի AddingFunc(x, y)
արտահայտությունը ունենա նույն տիպը ինչ lhs + rhs
, x
և y
արգումնենտերը փոխանցելու դեպքում, C++11 լեզվում կարելի է օգտագործել հետևյալ հայտարարությունը.
template <typename LHS, typename RHS>
decltype(std::declval<const LHS &>() + std::declval<const RHS &>())
AddingFunc(const LHS &lhs, const RHS &rhs)
{
return lhs + rhs;
}
Իհարկե ավելի լավ կլիներ std::declval<const LHS &>()
և std::declval<const RHS &>()
փոխարեն օգտագործել համապատասխանաբար lhs
և rhs
։ Սակայն հաջորդ տարբերակում,
template <typename LHS, typename RHS>
decltype(lhs + rhs) AddingFunc(const LHS &lhs, const RHS &rhs) // Թույլատրելի չէ C++11-ում
{
return lhs + rhs;
}
որը ավելի պարզ է թվում, lhs
և rhs
իդենտիֆիկատորները օգտագործվում են decltype
օպերանդում և չեն կարող օգտագործվել, որպես պարամետրներ որոնք ավելի ուշ են հայտարարվելու։ Այս խնդրի լուծման համար C++11 ստանդարտում նախատեսված է ֆունկցիաների գրելաձևի նոր տարբերակ, որում կարելի է ֆունկցիայի վերադարձվող տիպը նշել վերջում։
template <typename LHS, typename RHS>
auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs + rhs)
{
return lhs + rhs;
}
Պետք է նշել, որ վերոհիշյալ գրելաձևը որոշ դեպքերում բավականին երկար է ստացվում։
template <typename LHS, typename RHS>
auto AddingFunc(LHS &&lhs, RHS &&rhs) ->
decltype(std::forward<LHS>(lhs) + std::forward<RHS>(rhs))
{
return std::forward<LHS>(lhs) + std::forward<RHS>(rhs);
}
template <typename LHS, typename RHS>
auto AddingFunc(LHS &&lhs, RHS &&rhs) ->
decltype(std::declval<LHS>() + std::declval<RHS>()) // նույն արդյունքն է ինչ նախորդ դեպքում
{
return std::forward<LHS>(lhs) + std::forward<RHS>(rhs);
}
template <typename LHS, typename RHS>
decltype(std::declval<LHS>() + std::declval<RHS>()) // նույն արդյունքն է,ինչ տիպը վերջ տեղափոխելու դեպքում
AddingFunc(LHS &&lhs, RHS &&rhs)
{
return std::forward<LHS>(lhs) + std::forward<RHS>(rhs);
}
Նոր գրելաձևը կարելի է օգտագործել ավելի պարզ հայտարարություններում ու նկարագրություններում։
struct SomeStruct
{
auto FuncName(int x, int y) -> int;
};
auto SomeStruct::FuncName(int x, int y) -> int
{
return x + y;
}
auto
բանալի բառի (Անգլերեն. keyword) օգտագործումը նշանակում է որ վերադարձվող տիպը ավելի ուշ է նշվելու, այլ ոչ թե ավտոմատ կերպով դուրս է բերվելու։
Օբյեկտների կոնստրուկտորների բարելավում
խմբագրելՍտանդարտ C++ թույլ չի տալիս դասի մի կոնստրուկտորի միջից այդ նույն դասի այլ կոնստրուկտոր կանչել, կոնստրուկտորները պետք է իրենց մեջ ինիցիալիզացնեն դասի բոլոր տվյալները, կամ դա անեն դասի այլ մեթոդի օգնությամբ։ Ոչ կոնստանտ տվյալները չեն կարող ինցիալիզացվել հենց հայտարարման պահին։
C++11 օգնում է ազատվել այս խնդիրներից։
Նոր ստանդարտը թույլ է տալիս դասի մի կոնստրուկտորից նույն դասի այլ կոնստրուկտոր կանչել։ Սա թույլ է տալիս գրել կոնստրուկտորներ, որոնք կրկնում են այլ կունստրուկտորների վարքը, առանց ավելորդ անգամ կոդը կրկնելու։
Օրինակ.
class SomeType {
int number;
public:
SomeType(int new_number) : number(new_number) {}
SomeType() : SomeType(42) {}
};
Օրինակից պարզ է, որ SomeType
առանց արգումենտների կոնստրուկտորը կանչում է նույն դասի մեկ այլ կոնստրուկտոր, որի միջոցով ինիցիալիզացնում է number
փոփոխականը։ Նույն արդյունքին կարելի է հասնել, եթե 42 արժեքը փոփոխականին տրվեր հենց սահմանման ժամանակ։
class SomeType {
int number = 42;
public:
SomeType() {}
explicit SomeType(int new_number) : number(new_number) {}
};
Դասի ցանկացած կոնստրուկտոր number փոփոխականին կվերագրի 42 արժեքը, եթե հենց ինքը ուրիշ արժեք չվերագրի։ Հատկանշական է, որ C++03 համարում էր օբյեկտը կառուցված, եթե կոնստրուկտորը ավարտել է աշխատանքը, ապա C++11 գոնե մեկ դելեգատ կոնստրուկտորի աշխատանքի ավարտից հետո մնացած կոնստրուկտորները կաշխատեն արդեն ամբողջովին կառուցված օբյեկտի վրա։ Անկախ դրանից ժառանգ դասի կոնստրուկտորները կաշխատեն միայն ծնող դասի բոլոր կոնստրուկտորների ավարտից հետո։
Վիրտուալ ֆունկցիաների բացահայտ վերասահմանում
խմբագրելՀնարավոր է, որ վիրտուալ մեթոդը փոփոխվի ծնող դասում, կամ ի սկզբանե սխալ գրված լինի ժառանգ դասի մեջ։ Նման դեպքերում այդ ֆունկցիան ժառանգ դասի մեջ չի փոխարինի ծնող դասի համապատասխան ֆունկցիային։ Հակառակ դեպքում, եթե ծրագրավորողը ճիշտ չնշի ֆունկցիաները ժառանգ դասերում, ապա մեթոդը սխալ կիրականացվի ծրագրի կատարման ընթացքում։ Օրինակ.
struct Base {
virtual void some_func();
};
struct Derived : Base {
void sone_func();
};
Այստեղ ժառանգ դասում սահմանված ֆունկցիայի մեջ թույլ է տրված սխալ, որի հետևանքով այդ ֆունկցիան չի վերասահմանում ծնող դասի Base::some_func
, և համապատասխանաբար չի կանչվի պոլիմորֆիկ կերպով ցուցչի կամ հղման միջոցով որը ցուցում/հղվում է բազային օբյեկտի վրա։
C++11 ստանդարտում նոր հնարավորություն կա նման թերությունները հայտնաբերել կոմպիլյացիայի ընթանցքում։ Հակառակ համատեղելիության պատճառով այս հնարավորությունը համարվում է ընտրովի։
Օրինակ.
struct B
{
virtual void some_func();
virtual void f(int);
virtual void g() const;
};
struct D1 : public B
{
void sone_func() override; // սխալ է` ֆունկցիայի սխալ անուն
void f(int) override; // ճիշտ է` վերասահմանում է համապատասխան ֆունկցիան
virtual void f(long) override; // սխալ է` պարամետրերի անհամապատասխան տիպեր
virtual void f(int) const override; // սխալ է`ֆունկցիայի անհամապատասխան cv-կվալիֆիկացիա
virtual int f(int) override; // սխալ է` անհամապատասխան վերադարձվող տիպ
virtual void g() const final; // ճիշտ է` վերասահմանում է համապատասխան ֆունկցիան
virtual void g(long); // ճիշտ է` նոր վիրտուալ ֆունկցիա
};
struct D2 : D1
{
virtual void g() const; // սխալ է` փորձում է վերասահմանել վերջնական վերսահմանված ֆունկցիան
};
Ֆունկցիայի մոտ final
բանալի բառի առկայությունը նշանակում է, որ այն հետագայում չի կարելի վերասահմանել։ Բացի այդ final
բանալի բառի միջոցով սամանված դասից չի կարելի ժառանգել։
struct F final
{
int x, y;
};
struct D : F // սխալ է` final դասերից ժառանգումը արգելված է
{
int z;
};
Զրոյական ցուցիչի հաստատուն
խմբագրելԴեռևս 1972 թ. C լեզվում 0
կոնստանտը երկու դեր ուներ՝ ամբողջ թիվ և զրոյական ցուցիչ։ Այդ անորոշության լուծումներից մեկը NULL
մակրոսն է, որը սովորաբար ((void*
)0) կամ 0
է։ C++ այս մասով տարբերվում է C-ից նրանով, որ միայն 0
-ն կարող է հանդես գալ որպես զրոյական ցուցիչ։ Դա ֆունկցիաների գերբեռնման ժամանակ բերում է խնդիրների։
void foo(char *);
void foo(int);
Եթե NULL
սահմանված է 0
, ապա foo(NULL)
կանչի դեպքում կաշխատի foo(int)
ֆունկցիան, այլ ոչ թե foo(char *)
, ինչպես կարելի էր ենթադրել կոդի հպանցիկ դիտումից հետո, որտեղ, ամենայն հավանականությամբ ծրագրավորողը նկատի էր ունեցել հենց foo(char *)
ֆունկցիայի կանչը։
C++11 ստանդարտի նորարություններից մեկն այն է, որ զրոյական ցուցիչները նկարագրելու համար ավելացել է nullptr
բանալի բառը։ Այս կոնստանտը std::nullptr_t
տիպի է, որը կարելի է բերել ցանկացած ցուցչային տիպի և համեմատել ցանկացած ցուցչի հետ։ Սակայն այս դեպքում ամբողջ տիպի բերում չի կատարվում։ Բացառություն է կազմում միայն bool
տիպը։
Հակադարձ համատեղելիության ապահովման համար 0
կոնստանտը նույնպես կարելի է օգտագործել, որպես զրոյական ցուցիչ։
char *pc = nullptr; // ճիշտ է
int *pi = nullptr; // ճիշտ է
bool b = nullptr; // ճիշտ է. b = false.
int i = nullptr; // սխալ է
foo(nullptr); // կանչվում է foo(char *), այլ ոչ թե foo(int);
Խիստ տիպայնացված թվարկումներ
խմբագրելՍտանդարտ C++-ում թվարկումները(Անգլերեն. enumeration) տիպերի բերման տեսանկյունից ապահով չեն։ Իրականում նրանք ներկայացված են ամբողջ թվերի տեսքով, անկախ նրանից, որ տարբեր թվարկումները իրականում տարբեր տիպեր են։ Դա իր հերթին թույլ է տալիս երկու տարբեր թվարկումների միջև համեմատություններ անել։ Միակ հնարավորությունը, որ տրամադրում է C++03-ը, թվարկումների ապահովության համար՝ չի թույլատրվում ամբողջ տիպերից կամ թվարկումներից անուղղակի տիպերի բերումը այլ թվարկումների։ Բացի այդ հիշողության մեջ (ամբողջ տիպերի տեսքով) ներկայացումը կախված է իրականացումից և ապահով չէ։ Վերջապես թվարկումները ունեն ընդհանուր տեսանելիության տիրույթ, որ թույլ չի տալիս տարբեր թվարկումների մեջ լինեն նույն անունով անդամներ։
C++11 տրամադրում է հատուկ տեսակի դասակարգումներ նման տիպի թվարկումների համար, ինչը ազատում է վերոհիշյալ թերություններից։ Նման դեպքերում օգտագործում են enum class
հայտարարությունը (ինչպես նաև կարելի է օգտագործել enum struct
հայտարարությունը)։
enum class Enumeration {
Val1,
Val2,
Val3 = 100,
Val4, /* = 101 */
};
Այսպիսի թվարկումը տիպերի բերման տեսանկյունից համարվում է ապահով։ Դասային տիպի թվարկումները հնարավոր չէ ոչ բացահայտ կերպով բերել ամբողջ թվերի։ Որպես հետևանք արգելված է թվարկումնեի համեմատումը ամբողջ թվերի հետ։
Այժմ թվարկումները տիպը կախված չեն իրականացումից։ Լռելիությամբ ինչպես վերոհիշյալ դեպքն է՝ տիպը int
է, բայց մնացած դեպքերում այդ տիպը կարելի նշել ինքնուրույն հետևյալ կերպ.
enum class Enum2 : unsigned int {Val1, Val2};
Թվարկումների անդամների հասանելիության տիրույթը համընկնում է հենց թվարկման հասանոլիության տիրույթի հետ։ Տարբեր էլեմենտներ օգտագործելու համար պետք է նշել նաև նրա տիպը՝Enum2::Val1
, իսկ ուղղակի Val1
-ի տիպը որոշված չէ։
Սովորական թվարկումների դեպքում C++11 թույլ է տալիս նշել տեսանելության տիրույթը և տիպը։
enum Enum3 : unsigned long {Val1 = 1, Val2};
Տվյալ օրինակում թվարկման էլեմենտները սահմանված են Enum3
տեսանելիության տիրույթում (Enum3::Val1
), բայց համատեղելիության ապահովման նպատակով դրանք հասանելի են նաև ընդհանուր տեսանելիության տիրույթում։
Ինչպես նաև C++11 ստանդարտում թվարկումները կարելի է նախօրոք հայտարարել, ինչը նախորդ և ոչ մի ստանդարտում չէր թույլատրվում, քանի որ թվարկման չափը ուղղակիորեն կախված է իր անդամներից։ Նման հայտարարություններից կարելի է օգտվել միայն թվարկման չափը նշելու դեպքում։
enum Enum1; // սխալ է, տիպը որոշված չէ։
enum Enum2 : unsigned int; // ճիշտ է C++11-ում, տիպը նշված է։
enum class Enum3; // ճիշտ է C++11-ում, լռելիությամբ տիպը — int:
enum class Enum4 : unsigned int; // ճիշտ է C++11-ում։
enum Enum2 : unsigned short; // սխալ է C++11-ում, քանի որ այս տիպը արդեն հայտարարված է։
Անկյունավոր փակագծեր
խմբագրելC++-ի ստանդարտ շարահյուսական վերլուծիչները (անգլ. parser) «>>
» կոմբինացիան ընդունում են որպես աջ տեղաշարժի օպերատոր։ Ներդրված կաղապար արգումենտների դեպքում բացատանիշի բացակայությունը այդ փակագծերի միջև համարվում է սխալ։
C++11-ը լավացնում է վերլուծիչի վարքը այդպիսի դեպքերում, այսինքն մի քանի աջ եզրային փակագծերը կընկալվեն որպես կաղապար արգումենտների ցուցակի ավարտ։
Նկարագրված վարքը կարելի է ձևափոխել ի օգուտ նախկինի՝ կլոր փակագծերի միջոցով։
template<class T> class Y { /* ... */ };
Y<X<1>> x3; // ճիշտ է, նույնն է, ինչ "Y<X<1> > x3;".
Y<X<6>>1>> x4; // սխալ է, պետք է լինի "Y<X<(6>>1)>> x4;".
Ինչպես երևում է վերը նշված օրինակում այս փոփոխությունը այնքան էլ համատեղելի չէ նախկին ստանդարտի հետ։
Բացահայտ տիպերի բերման օպերատորներ
խմբագրելC++ Ստանդարտ-ում գոյություն ունի explicit
բանալի բառը, որը կարելի է օգտագործել մեկ պարամետրներով կոնստրուկնորների հետ, որպեսզի դրանք չոգտագործվեն ոչ բացահայտ տիպերի բերման ընթացքում։ Սակայն դա չի ազդում իրական տիպերի բերման օպերատորների վրա։ Դասերը կարող են ունենալ operator bool()
օպերատորը, և այդ դեպքում ճիշտ կլինի if
(variable
) արտահայտությունը։ Խնդիրն այն է, որ այդ օպերատորը չի պաշտպանում այլ չկանխատեսված ձևափոխումներից։ Քանի որ C++-ում bool
համարվում է մաթեմատիկական տիպ, այդ իսկ պատճառով այն կարելի բերել ցանկացած թվի տիպի։
C++11 ստանդարտում explicit
բանալի բառը կիրառելի է նաև ձևափոխման օպերատորների հետ։ Ինչպես կոնստրուկտորների դեպքում այնպես էլ այստեղ այն պաշտպանում է չնախատեսված ձևափոխումներից։ Սակայն այն դեպքերում, երբ ծրագիրը ակնկալում է bool
տիպ, ձևափոխությունը տեղի է ունենում։
Կաղապարներում typedef
խմբագրելC++ ստանդարտում հնարավոր է typedef
բառի միջոցով ստեղծել հոմանիշներ այլ տիպերի համար, այդ թվում օգտագործվում է կաղապարների համար՝ հոմանիշների միջոցով վերցնելով նրա բոլոր պարամետրերը։ Բայց հնարավոր չէ ստեղծել կաղապարի հոմանիշ։
Օրինակ.
template<typename First, typename Second, int third>
class SomeType;
template<typename Second>
typedef SomeType<OtherType, Second, 5> Typedef Name; // հնարավոր չէ C++-ով
Սրա կոմպիլյացիան չի անցնի։
C++11-ում ավելացվել է այս հնարավորությունը հետևյալ կերպ.
template<typename First, typename Second, int third>
class SomeType;
template<typename Second>
using Typedef Name = SomeType<OtherType, Second, 5>;
C++ 11-ում using
հրահանգով հնարավոր է ստեղծել կեղծանուն տվյալների տիպերի համար։
Typedef void (*OtherType)(double); // հին ձևը
using OtherType =void (*)(double); // նոր ձևը
Հեռացնել սահմանափակումները union-ի միջոցով
խմբագրելՆախորդ C++ ստանդարտներում գոյություն ուներ մի շարք սահմանափակումներ, միավորումներում (անգլ. union) դասերի տիպեր օգտագործելիս։ Հաճախ միավորվող օբյեկտները չեն կարող պահել ոչ դատարկ կոնստրուկտորները։ C++ 11-ը բացառում է այդպիսի սահմնափակումների մի մասը։ Ահա մի պարզ օրինակ, որը թույլ է տալիս C++ 11։
// placement new
#include <new>
struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union U {
int z;
double w;
Point p; // սխալ է '''[[C++03]]''' համար, քանի որ կա ոչ դատարկ կոնստրուկտոր։ Սակայն այն ճիշտ աշխատում է C++11-ում.
U() { new( &p ) Point(); } //Միավորման ընթացքում չեն սահմանվում ոչ պարզ մեթոդները։
// Անհրաժեշտության դեպքում դրանք կարելի է հեռացնել, որպեսզի աշխատի մեխանիկական միավորումը
};
Փոփոխությունները չեն ազդում առկա կոդի վրա, քանի որ այն միայն փոքրացնում են տվյալ սահմանափակումները։
Հատուկ իմաստ կրող անուններ
խմբագրելoverride
և final
իդենտիֆիկատորները հատուկ իմաստ ունեն այն դեպքերում, միայն որոշակի դեպքերում։ Մնացած դեպքերում դրանք օգտագործվում են ինչպես սովորական իդենտիֆիկատորներ (ինչպես օրինակ փոփոխականի կամ ֆունկցիյաի անունը)։
Ընդլայնված ֆունկցիոնալություն
խմբագրելԱյս բաժնում սահմանվում են նոր հնարավորություններ, որը առաջ հնարավոր չէր օգտագործել կամ էլ պետք էր հատուկ հնարավորություններով գրադարաններ։
Անորոշ քանակի պարամետրերով կաղապարներ
խմբագրելՄինչև C++ 11֊-ը կաղապարները (նաև ֆուկցիաները և դասերը) կարող էին ունենալ միայն որոշակի ֆիքսված թվով պարամետրեր, որոնք հայտարարվում էին կաղապարի նկարագրության մեջ։ C++ 11֊ը թույլ է տալիս ստեղծել տարբեր տիպեր և անորոշ քանակով պարամետրեր ունեցող կաղապարներ։ Օրինակ․
template<typename... Values>
class tuple;
tuple
(շարք, -յակ) կաղապար ֊դասը որպես կաղապարի անդամներ ընդունում է ցանկացած թվով տիպերի անուներ։
class tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> some_instance_name;
փոփոխականները կարող են նաև բացակայել․ class tuple<> some_instance_name
նույնպես կաշխատի։
Որպեսզի կաղպարն առանց փոփոխականի տեղի չունենա, ապա պետք է գրել հետևյալ կերպ.
template<typename First, typename... Rest>class tuple;
Փոփոխվող արգումենտների քանակով կաղապարները կիրառելի են նաև ֆունկցիաների դեպքում, ինչը թույլ է տալիս օգտագործել դրանք անորոշ քանակի արգումենտներ ունեցող ֆունկցիաներում(օրինակ՝ printf), և ոչ պարզ օբյեկտներ կառուցելու ընթացքում։
template<typename... Params>void printf(const std::string &str_format, Params... parameters);
...
այս օպերատորը այստեղ ունի երկու իմաստ։ Params
-ից ձախ գտնվելու դեպքում օպերատորը նշանակում է, որ պարամետրերը վերջավոր են։ Օգտագործելով վերջավոր պարամետրերի գաղափարը կաղապարում հնարավոր է հայտարարել 0 և ավելի փոփոխականներ։ Վերջավոր պարամետրերը օգտագործում են ոչ միայն տիպերի անուները փոխանցելու համար։ ...
այս օպերատորը աջից իր հերթին թույլ է տալիս պարամետրերը հանդես գան առանձին փոփոխականներ ( ինչպես ցույց է տրված ներքևի օրինակում args...
-ի համար) ։
Կաղապարները փոփոխվող արժեքների դեպքում նույնպես հնարավոր է օգտագործել ռեկուրսիվ։ Այդպիսի օրինակ է printf
ֆունկցիան.
Void printf(const char*s)
{
while (*s) {
if (*s =='%'&&*(++s) !='%')
throw std::runtime_error("invalid format string: missing arguments");
std::cout <<*s++;
}
}
template<typename T, typename... Args>
void printf(constchar*s, T value, Args... args)
{
while (*s) {
if (*s =='%'&&*(++s) !='%') {
std::cout << value;
++s;
printf(s, args...); // շարունակում է հավաքումը
/ / փոփոխականների , նույնիսկ այն դեպքում երբ *s == 0
return;
}
std::cout <<*s++;
}
throw std::logic_error("extra arguments provided to printf");
}
Այս կաղապարը հանդիսանում է ռեկուրսիվ։ Ուշադրություն դարձրեք, որ printf
ֆունկցիան կանչում է արդյունքը ինքն իր կամ բազային printf
-ի այն դեպքում երբ args...
-ն դատարկ է։
Չկա այնպիսի պարզ մեխանիզմ որով հնարավոր կլինի շրջանցել կաղապարների փոփոխվող արժեքների պարամետրերը։ Չնայած այդ բանին օգտագործելով փոփոխականների օպերատորը թույլ է տալիս շրջանցել այդ խնդիրը։
Օրինակ դասը հնարավոր է ներկայացնել հետևյալ կերպ.
template<typename... BaseClasses>classClassName:public BaseClasses... {
public:
ClassName (BaseClasses&&... base_classes) : BaseClasses(base_classes)... {}
};
Փոփոխականների օպերատորը կրկնօրինակում է բոլոր տիպերը ծնող դասի ClassName
հետևյալ կերպ, դասը պետք է ժառանգի բոլոր տիպերից որոնք նկարագրվել են կաղապարի պարամետրերում։
Բացի այդ կոնստրուկտորը պետք է ընդունի հղում բոլոր բազային դասերից, որպեսզի սկզբնական բազային-ծնող դասի ClassName
-ի համար իրականցվի։
Կաղապարի պարամետրերը կարելի է վերահղվել։ Համատեղելով հղումնները rvalue
-ի հետ ( նայեք վերևում ) հնարավոր է իրակնացնել վերահղում։
template<typename TypeToConstruct> struct SharedPtrAllocator {
template<typename ...Args> std::shared_ptr<TypeToConstruct> construct_with_shared_ptr(Args&&... params) {
return std::shared_ptr<TypeToConstruct>(new TypeToConstruct(std::forward<Args>(params)...));
};
};
Հետևյալ կոդը իրականացվում է փոփոխականների վերաբաշխումը ըստ ցուցակի TypeToConstruct
կոնստրուկտորում։std::forward<Args>(params)
գրելաձևը թույլ է տալիս թափանցիկ վերահղել փոփոխականները կոնստրուկտորում, առանց հաշվի առնելու rvalue
հատկանիշը։ Ֆունկցիան իքնաբերաբար դառնում է ցուցիչ std::shared_ptr
որպեսզի ապահովի հիշողության կորստից։
Հնարավորություն կա համատեղել փոփոխականների թիվը հետևյալ կերպ։
template<typename ...Args> struct SomeStruct {
staticconstint size =sizeof...(Args);
};
Այստեղ SomeStruct<Type1, Type2>::size
հավասար է 2, իսկ SomeStruct<>::size
հավասար է 0։
Նոր տողային լիտերալներ
խմբագրելC++03 տրամադրում էր երկու տիպի տողային լիտերալներ։ Առաջինը տիպը իրենից ներկայացնում է երկու ապաթարցերում գրված զանգված որը ավարտվում է զրոյով (null-terminated
) տիպը const char
։ Երկրորդ տիպը սահմանվում է որպես L"", զանգված, որը ավարտվում է զրոյական սիմվոլով, տիպը const wchar_t
, որտեղ wchar_t
հանդիսանում է չսահմանված չափով և քերականությամբ սիմվոլ։ Ոչ մի տիպի լիտերալ չի սպասարկում UTF-8
, UTF-16
, կամ ցանկացած այլ տիպի Unicode
կոդավորելուն։
char
տիպի չափը այնքան փոքր է, որ կարողանա իր մեջ պահել 8 բիտ հիշողությամբ UTF-8 տիպի ցանկացած սիմվոլ, և այնքան մեծ որ կարողանա իր մեջ պահել ծրագրի կատարման ընթացքում ստացված ցանկացած սիմվոլ։
Գոյություն ունի երեք տիպի Unicode կոդավորում, որը աջակցում է C++11 ստանդարտը, դրանք են. UTF-8, UTF-16 և UTF-32։ Որպես լրացում վերոնշյալ փոփոխություններին C++11 ստանդարտում ավելացել են ևս երկու սիմվոլային տիպ՝ char16_t
և char32_t
։ Նրանք օգտագործվում են UTF-16
և UTF-32
սիմվոլները պահելու համար։
Ներքևում ցույց է տրված թե ինչպես ստեղծել լիտերալներ այս բոլոր կոդավորումների համար։
u8"I'm a UTF-8 string."
u"This is a UTF-16 string."
U"This is a UTF-32 string."
Առաջին տողում գրվածը սովորական const char[]
: Երկրորդ տողինը —
const char16_t[]
. Երրորդ տողինը — const char32_t[]
.
Լիտերալների կառուցման համար ստանդարտ <span style="color:blue"Unicode-ում, հաճախ անհրաժեշտ է լինում Unicode –ի կոդը դնել հենց տողում։ Դրա համար C++11-ը առաջարկում է հետևյալ գրելաձևերը.
u8"This is a Unicode Character: \u2018."
u"This is a bigger Unicode Character: \u2018."
U"This is a Unicode Character: \U00002018."
\u-ից հետո պետք է թվերը լինեն տասնվեցական, պետք չէ օգտագործել պրեֆիքս 0x։ Ինդետիֆիկատոր \u նշանակում է 16-բիտային Unicode կոդ, իսկ 32-բիտային կոդով գրելու համար օգտագործվում է \U և 32-բիտային տասնվեցական թիվ։ Հնարավոր է գրել միայն ճշգրիտ Unicode կոդեր։ Օրինակ, կոդը այս ֆորմատով U+D800–U+DFFF արգելվում է, քանի որ նրանք վերապահված են փոխնակ զույգերի UTF-32-ում։ Երբեմն օգտակար է լինում խուսափել տողերի հետ ձեռքով աշխատել, հատկապես երբ օգտագործվում է լիտերալ XML տիպի ֆայլը, սկրիպտային ծրագրավորման լեզուներում, կամ կանոնավոր արտահայտություններում։ Այդ նպատակով C++11-ը ստանդարտը աջակցում է «հում(сырые)» տողային լիտերալներին։
R"(The String Data \ Stuff " )"
R"delimiter(The String Data \ Stuff " )delimiter"
Առաջին դեպքում ամեն ինչ գտնվում է այս սիմվոլների մեջ "( և )", որը մասն է կազմում տողի։ Սիմվոլները " և \ պետք չէ էկրանին ցուցադրել։ Երկրորդ դեպքում "delimiter
( սկիզբն է տողի և այն վերջանում է)delimiter"
-ով։ delimiter
տողը կարող է ունենալ 0-ից մինչև 16 երկարությամբ սիմվոլներ։ Այս տողը չի կարող պարունակել բացատանիշեր, ղեկավարվող սիմվոլները, '(', ')', կամ այս սիմվոլը '\' Օգտագործել այս տող-անջատիչները հնարավոր է օգտագործել ')' այս սիմվոլը «հում(сырые)» >> տողային լիտերալներում։ Օրինակ R"delimiter
((a-z))delimiter"
համարժեք է "(a-z)"
:
«հում(сырые)» տողային լիտերալները կարող են միավորվել ընդյլանված շարքի լիտերալների հետ ( պրեֆիքս L"") կամ ցանկացած այլ պրեֆիքս որը Unicode
–ի լիտերալ է։
LR"(Raw wide string literal \t (without a tab))"
u8R"XXX(I'm a "raw UTF-8" string.)XXX"
uR"*(This is a "raw UTF-16" string.)*"
UR"(This is a "raw UTF-32" string.)"
Տիպ long long int
խմբագրել
Ամբողջ տիպ long long int
-ը նշված է C99 ստանդարտում և այն լայնորեն օգտագործվում է C++-ում, վերջապես այն հաստատել են C++ 11 ատանդարտում։
Ստատիկ ախտորոշում
խմբագրելC++11-ը ունի երկու ձև ստատիկ ախտորոշման.
- Հատուկ անունի
static_assert
այն տալիս է սխալը կոմպիլատորին եթե փակագծերում արտահայտությունը կեղծ է։ - Գրադարան
type_traits
, որը պարունակում է կաղապար, տրամադրում է տեղեկատվություն հենց կոմպիլացիա ժամանակ։
#include <type_traits>
template<class T>
void run(T *aData, size_t n)
{
static_assert(std::is_pod<T>::value, "Тип T должен быть простым.");
...
}
Աշխատանքը sizeof
-ի հետ դասի տարրերի հետ առանց օբյեկտ ստեղծելու
խմբագրել
C++ 03 ստանդարտը թույլ էր տալիս օգտագործել sizeof
-ը պարզ տիպերի և օբյեկտների համար։Բայց հետևյալ գրառումը չէինք կարող։
struct SomeType
{
OtherType member;
};
sizeof(SomeType::member); //չի աշխատում '''[[C++03]]''', բայց ճիշտ է աշխատում '''C++11''':
Այս կանչի արդյունքում պետք է հաշվել SomeType-ի
չափը։ C++ 03-ով կոդը կոմպիլացիա չէր լինում։ Իսկ C++ 11-ում այսպիսի գրելաձևը ընդունելի է, խնդիր չի առաջանում։
Փոփոխություններ C++-ի ստանդարտ գրադարաններում
խմբագրելՓոփոխություններ եղած բաղադրիչներում
խմբագրելstd::set
-ում օգտագործվում է «հուշում» (hint). այստեղ «հուշման» իմաստը փոխվել է։ Առաջ այն տարրն էր մինչև ընթացիկը և հարց էր առաջանում ինչ անել երբ ունես մեկ տարր, իսկ հիմա այն ընթացիկից հետո է։- C++֊-ն արդեն կարողանում է կոնստրուկտրը կանչել առանց հիշողություն հատկացնելու․ հետևյալ կերպ՝
std::allocator_traits<>::construct()
։ Համապատասխանաբար առաջացել է նոր մեթոդ՝emplace
, որը տեղում է ստեղծում օբյեկտը։ - Ավելացել է նոր լեզվական հնարավորություններ C++11 ստանդարտում։
- Ավելացվել են
cbegin
ևcend
, որոնցով երաշխավորված է ստեղծել հաստատուն իտերատորներ։ Հարմար է մետածրագրավորման համար, որում տիպերը ստեղծում ենauto
-ի միջոցով։ - Կոնտեյներներում, որոնք պահուստային հիշողություն են վերցնում, ավելացվել է
shrink_to_fit
ֆունկցիան։ std::list
-ից պահանջում են ավելին քան այն որ այն կատարվում էO(n)
ժամանակում, հասել են նրան, որ այն կատարվում է հաստատուն ժամանակում։std::vector
-ում ավելացվել է ֆունկցիաdata()
, որը կարողանում է դիմել միանգամից հիշողությանը։- Արգելել են մի քանի
std::string
-ների միաժամանակ հղվել նույն հիշողության վրա։ Դրա շնորհիվ այժմ հնարավոր է միանգամից դիմելfront()
-ին, օրինակ փոխգործակցությունըstring
-ի ևWinAPI
-ի։
Հեշ-աղյուսակներ
խմբագրելԵրկար ժամանակ է, որ STL-ի ստանդարտի մեջ չէին ընդգրկվում std::hash_set
և std::hash_map
դասերը, այնինչ դրանք փաստացի իրականացվում էին գրեթե բոլոր կոմպիլյատորներում։ C++11 ստանդարտում այս դասերն ընդգրկված են ստանդարտի մեջ և հանդես են գալիս unordered_set
և unordered_map
անուներով։ Չնայած, ըստ հեշ-աղյուսակների էությանը ստանդարտը շատ թույլ չի տալիս մանևրելու, անուները նման են C++֊ի ոճին. ոչ թե «ինչպես են նրանք իրականացրել», և «ինչ են իրենցից ներկայացնում»։
Կանոնավոր արտահայտություններ
խմբագրելՆոր գրադարանը, որը սահմանված է <regex>
վերնագրային ֆայլում, ներառում է մի քանի նոր դասեր.
- Կանոնավոր արտահայտություններում ցույց են տրված է «օրինակ» դասերով, հետևյալ կերպ՝
std::regex
։ - Արդյունքների որոնումը ցույց են տրված էկզեմպլիառ կաղապարներով հետևյալ կերպ՛
std::match_results
։
std::regex_search
այս ֆունկցան օգտագործում են փնտրելու համար։ Գտնելու և փոխելու համար օգտագործվում է std::regex_replace
ֆունկցիան։ ֆունկցիան վերադարձնում է տողը հետո փոխարինում։
Ալգորիթմներ std::regex_search
և std::regex_replace
ստանում են մուտք գործել կանոնավոր արտահայտություններ և տողեր, վերադարձնում են արդյունքը էկզեմպլիառի տեսքով std::match_results
:
Օրինակ օգտագործենք std::match_results
.
constchar*reg_esp ="[ ,.\\t\\n;:]"; // ցուցակը անջատիչ սիմվոլների
// նույն հնարավոր է անել օգտագործելով "հում(сырые)" տողերը։
// const char *reg_esp = R"([ ,.\t\n;:])";
std::regex rgx(reg_esp); // 'regex' – սա էկզեմպլառ է կաղապար դասով
// 'basic_regex' կաղապար է 'char' պարամետրով։
std::cmatch match; // 'cmatch' - սա էկզեմպլառ է կաղապար դասով
// 'match_results' կաղապար է 'const char *' պարամետրով։
constchar*target ="Unseen University - Ankh-Morpork";
// Ֆիքսում է բոլոր 'target' բառերը տողում անջատելով սիմվոլներով 'reg_esp':
if( std::regex_search( target, match, rgx ) ) {
// եթե բառ է, անջատիչ սիմվոլը գտնվում է տողում։
constsize_t n = match.size();
for( size_t a =0; a < n; a++ ) {
std::string str( match[a].first, match[a].second );
std::cout << str <<"\n";
}
}
Ծանոթագրություններ
խմբագրել- ↑ «We have an international standard: [[C++]]0x is unanimously approved». Վերցված է 2011 թ․ օգոստոսի 12-ին.
{{cite web}}
: URL–wikilink conflict (օգնություն) - ↑ Sutter, Herb (August 18, 2014), We have [[C++14]]!, Վերցված է 2014 թ․ օգոստոսի 18-ին
{{citation}}
: URL–wikilink conflict (օգնություն) - ↑ Stroustrup, Bjarne. «C++11 FAQ». stroustrup.com.
- ↑ «C++11 Overview: What specific design goals guided the committee?». Standard C++.
- ↑ «Bjarne Stroustrup: A C++0x overview» (PDF). Վերցված է 2011 թ․ հունիսի 30-ին.