C++11

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до: навігація, пошук

C++11 — чинна версія стандарту мови C++, прийнята у серпні 2011 комітетом ISO зі стандартизації мови замість ISO/IEC 14882:2003 (С++03). Новий стандарт включає доповнення в ядрі мови та розширення STL, включаючи велику частину TR1 — окрім, можливо, бібліотеки спеціальних математичних функцій.

Враховуючи те, що стаття писалася під час ще не завершеної роботи над стандартом — тому стаття, можливо, не буде точно відповідати кінцевому варіанту стандарту. Остання версія майбутнього стандарту опублікована на сайті комітету ISO C++.

ISO / IEC JTC1/SC22/WG21 Комітет Стандартизації C++ мав намір опублікувати новий стандарт в 2009 році (відповідно стандарт, який зараз називають C++0x, повинен був називатися C++09). Щоб встигнути, Комітет вирішив зосередитися на пропозиціях, що надійшли до 2006 і ігнорувати новіші[1].

Мови програмування, такі як C++, проходять еволюційний розвиток своїх можливостей. Цей процес неминуче викликає проблеми сумісності з уже існуючим кодом. Відповідно до доповіді, зробленої Б'ярном Страуструпом (автор мови С++ та член Комітету), новий стандарт буде на 100% сумісний з нинішньою версією мови С++ [2]

Зміст

Передбачувані зміни стандарту[ред.ред. код]

Як вже було сказано, зміни торкнуться як ядра С++, так і його стандартної бібліотеки.

При розробці кожного розділу майбутнього стандарту комітет використав ряд правил:

  • Підтримка стабільності мови та забезпечення сумісності з C++98 і, по можливості, з Сі;
  • Бажаним є введення нових можливостей через стандартну бібліотеку, а не через ядро мови;
  • Бажано подавати зміни, які покращують техніку програмування;
  • Удосконалювати C++ з точки зору системного та бібліотечного дизайну, замість введення нових можливостей, корисних для окремих програм;
  • Збільшувати типобезпечність для запевнення безпечної альтернативи для нинішніх небезпечних підходів;
  • Збільшувати продуктивність і можливості працювати безпосередньо з апаратною частиною;
  • Забезпечувати рішення реальних поширених проблем;
  • Реалізувати принцип «не платити за те, що не використовуєш»;
  • Зробити C++ простішою для вивчення без видалення можливостей, які використовуються програмістами-експертами.

Приділяється увага новачкам, які завжди будуть становити більшу частину програмістів. Багато новачків не прагнуть поглиблювати рівень володіння С++, обмежуючись його використанням під час роботи над вузькими специфічними задачами [1] Крім того, з огляду на універсальність С++ і широке коло її використання (включаючи як різноманітність програм, так і стилів програмування), навіть професіонали можуть виявитися новачками при використанні нових парадигм програмування.

Розширення ядра С++[ред.ред. код]

Першочергове завдання комітету — розвиток ядра мови С++. Дата подання C++0x залежить від успіхів у цій частині стандарту.

Ядро буде значно вдосконаленим, буде додано підтримку багатопотоковості, підтримка узагальненого програмування, уніфікація ініціалізації та будуть проведені роботи з підвищення його продуктивності.

Для зручності, можливості ядра і його зміни розділені на три основні частини: підвищення продуктивності, підвищення зручності і нова функціональність. Окремі елементи можуть належати до декількох груп, але їх буде описано лише в одній — найпридатнішій.

Підвищення продуктивності за рахунок ядра мови[ред.ред. код]

Ці компоненти мови введені для зменшення витрат пам'яті або збільшення продуктивності.

Посилання на тимчасові об'єкти / Семантика переносу (Rvalue Reference/Move semantics)[ред.ред. код]

У стандарті C++ тимчасові об'єкти (оригінальний термін «R-values», оскільки вони породжуються з правого боку виразу) можна передавати у функції, але тільки як константні посилання (const &). Отже, функція не в змозі визначити, тимчасовий це об'єкт чи нормальний, який теж передали як const &. Крім того, об'єкт, переданий як const &, більше не можна модифікувати (легально).

У C++0x буде додано новий тип посилання — посилання на тимчасовий об'єкт (R-value reference). Його оголошення наступне: typename &&. Воно може бути використано як неконстантний об'єкт, який можна легально модифікувати . Це нововведення дозволяє враховувати тимчасові об'єкти і реалізовувати семантику переносу (Move semantics).

Наприклад, std::vector — це проста обгортка навколо Сі-масиву і змінної, що зберігає його розмір. Якщо std::vector створюється як тимчасовий об'єкт або повертається з функції — можна, створюючи новий об'єкт, просто перенести всі внутрішні дані із посилання нового типу. Конструктор перенесення std::vector через отримане посилання на тимчасовий об'єкт просто копіює вказівник масиву, що знаходиться в посиланні, яке після закінчення встановлюється в порожній стан.

Узагальнені константні вирази[ред.ред. код]

У C++ завжди була присутня концепція константних виразів. Так, вирази типу 3+4 завжди повертали одні й ті самі результати, не викликаючи жодних побічних ефектів. Самі собою вирази зі сталими надають компіляторам C++ зручні можливості для оптимізації результату компіляції. Компілятори обчислюють результати таких виразів лише на етапі компіляції і зберігають вже обчислені результати в програмі. Таким чином, подібні вирази обчислюються лише раз. Також існує кілька випадків, у яких стандарт мови вимагає використання константних виразів. Такими випадками, наприклад, можуть бути визначення масивів або значення перелічень (enum).

int GetFive () {return 5;}
int some_value [GetFive() + 7]; // створення масиву 12 цілих; заборонено в C++

Вищевказаний код заборонений в C++, оскільки GetFive() + 7 фактично не є константним виразом, відомим на етапі компіляції. Компілятору на цей момент просто не відомо, що функція насправді повертає константу під час виконання. Причиною таких міркувань компілятора є те, що ця функція може вплинути на стан глобальної змінної, викликати іншу константну функцію не часу виконання тощо.

C++11 вводить ключове слово constexpr, яке дозволяє користувачеві гарантувати, що функція чи конструктор об'єкта повертає константу часу компіляції. Код вище може бути переписаний в такий спосіб:

constexpr int GetFive () {return 5;}
 
int some_value [GetFive () + 7]; // створення масиву 12 цілих; дозволено в C++0x

Таке ключове слово дозволяє компілятору зрозуміти і упевнитися в тому, що GetFive повертає константу.

Використання constexpr породжує дуже жорсткі обмеження на дії функції:

  1. Така функція не може бути типу void.
  2. Тіло функції повинно бути виду return вираз .
  3. Вираз повинен також бути константою, а також може викликати лише ті функції, що також позначені ключовим словом constexpr, або просто використовувати звичайні константи.
  4. Функцію, позначену constexpr, не можна викликати до того моменту, поки вона не визначена у поточній одиниці компіляції.

Змінні також можуть бути визначені як значення константних виразів:

constexpr double accelerationOfGravity = 9.8;
constexpr double moonGravity = accelerationOfGravity / 6;

Такі змінні вже неявно вважаються позначеними ключовим словом const. У них можуть міститися лише результати константних висловів чи конструктори таких виразів.

У разі необхідності конструювання константних значень з типів, визначених користувачем, конструктори таких типів також можуть бути описані за допомогою constexpr. Конструктор константних виразів, подібно до константних функцій, також має бути визначений до моменту першого його використання у поточній одиниці компіляції. У такого конструктора повинно бути порожнє тіло, а також такий конструктор повинен ініціалізувати члени свого типу лише константами.

Зміни у визначенні простих даних (Plain Old Data або POD)[ред.ред. код]

У стандартному C++ лише структури, що задовольняють певному комплексу правил, можна розглядати як тип простих даних (plain old data type або POD). Існують вагомі причини очікувати розширення цих правил, для того, щоб більше типів розглядалися як POD. Типи, що задовольняють ці правилам, можна використовувати в реалізації об'єктного шару, сумісного з C. Однак, в C++03 список цих правил надмірно строгий.

C++0x послабить кілька правил, що стосуються визначення типів простих даних.

Клас / структура розглядаються як типи простих даних якщо вони тривіальні, стандартні (standard-layout???) і якщо всі їхні нестатичні члени також є типами простих даних.

Тривіальний клас чи структура задовольняють таким правилам:

  1. Вони можуть мати лише тривіальний конструктор за замовчуванням. Може бути використано синтаксис конструктора за замовчуванням (SomeConstructor() = default;).
  2. Вони можуть мати лише тривіальний конструктор копіювання за замовчуванням. Може бути використано той самий синтаксис.
  3. Вони можуть мати лише тривіальний оператор присвоєння копії.
  4. Вони можуть мати лише тривіальний деструктор, який не повинен бути віртуальним.

Стандартний клас чи структура задовольняють такі правила:

  1. Вони можуть мати лише статичні члени не стандартних типів.
  2. Вони мають один і той же специфікатор доступу (public, private, protected) для всіх нестатичних методів.
  3. Не мають віртуальних функцій.
  4. Не мають віртуальних класів-батьків.
  5. Їхні батьківські класи можуть бути лише стандартними.
  6. Не мають батьківського класу того ж типу, що і перший оголошений невіртуальний член.
  7. Або не мають батьківських класів, що містять нестатичні методи, або не містять членів нестатичних типів у більшості класів нащадків і не більше одного батьківського класу з нестатичними членами. Іншими словами, в ієрархії класів лише один клас може містити нестатичні члени.

Прискорення компіляції мови[ред.ред. код]

Зовнішні шаблони[ред.ред. код]

У стандартному С++ компілятор повинен інстанціювати шаблон щоразу, коли зустрічає в одиниці трансляції його повну спеціалізацію. Це може істотно збільшити час компіляції, особливо в тих випадках, коли шаблон інстанційований з однаковими параметрами у великій кількості одиниць трансляції. У цей час не існує способу вказати С++, що інстанціювання проводити не повинно.

У C++0x введена ідея зовнішніх шаблонів. У С++ вже є синтаксис для вказівки компілятору того, що шаблон повинен бути інстанційованим в певній точці:

template class std::vector <MyClass>;

У С++ не вистачає можливості заборонити компілятору інстанціювати шаблон в одиниці трансляції. C++0x просто розширює даний синтаксис:

extern template class std::vector <MyClass>;

Цей вираз говорить компілятору не інстанціювати шаблон в даній одиниці трансляції.

Покращення в практичному використанні мови[ред.ред. код]

Ці можливості призначені для того, щоб спростити використання мови. Вони дозволяють посилити типобезпечність, мінімізувати дублювання коду, ускладнюють помилкове використання коду тощо

Списки ініціалізації[ред.ред. код]

Концепція списків ініціалізації прийшла в C++ з C. Ідея полягає в тому, що структура або масив можуть бути створені передачею списку аргументів в порядку, відповідному порядку визначення членів структури. Списки ініціалізації рекурсивні, що дозволяє використовувати їх для масивів структур і структур, що містять вкладені структури. Списки ініціалізації дуже корисні для статичних списків і в тих випадках, коли потрібно ініціалізувати структуру певним значенням. C++ також містить конструктори, які можуть містити загальну частину роботи з ініціалізації об'єктів. Стандарт C++ дозволяє використовувати списки ініціалізації для структур і класів за умови, що ті відповідають визначенню простого типу даних (Plain Old Data — POD). Класи, що не є POD, не можуть використовувати для ініціалізації списки ініціалізації, у тому числі це стосується і стандартних контейнерів C++, таких, як вектори.

C++0x зв'язав концепцію списків ініціалізації і шаблонний клас, названий std::initializer_list. Це дозволило конструкторам і іншим функціям отримувати списки ініціалізації в якості параметрів. Наприклад:

class SequenceClass
{
public:
 SequenceClass (std:: initializer_list <int> list);
};

Цей опис дозволяє створити SequenceClass з послідовності цілих чисел таким чином:

SequenceClass someVar = {1, 4, 5, 6};

Цей конструктор є особливим різновидом конструкторів, які називають конструкторами за допомогою списків інціалізаціі. Класи, які містять подібні конструктори, обробляються особливим чином під час ініціалізації.

Клас std::initializer_list<> є типом, визначеним у стандартній бібліотеці C++0x. Однак, об'єкти даного класу компілятор C++0x може створити лише статично з використанням синтаксису з дужками {}. Список може бути скопійованим після створення, однак, це буде копіюванням за посиланням. Список ініціалізації є константним: ні його члени ні їхні дані не можуть бути змінені після створення.

Оскільки std::initializer_list<> є повноцінним типом, його можна використовувати не лише в конструкторах. Звичайні функції можуть отримувати типізовані списки ініціалізації як аргумент, наприклад:

void FunctionName (std:: initializer_list <float> list);
 
FunctionName ({1.0f,-3.45f,-0.4f});

Стандартні контейнери можуть бути ініціалізованими так:

std::vector<std::string> v = { "xyzzy", "plugh", "abracadabra"};
std::vector<std::string> v { "xyzzy", "plugh", "abracadabra"};

Універсальна ініціалізація[ред.ред. код]

У стандарті C++ міститься ряд проблем, пов'язаних з ініціалізацією типів. Існує кілька шляхів ініціалізації типів і не всі вони призводять до однакових результатів. Наприклад, традиційний синтаксис ініціюючого конструктора може виглядати як опис функції, і потрібно вжити додаткових заходів, щоб компілятор не помилився при аналізі. За допомогою ініціалізаторів агрегатів (виду SomeType var = {/*stuff*/};) можна ініціалізувати лише агрегуючі типи й POD.

C++0x надає синтаксис, що дозволяє використовувати єдину форму ініціалізації для всіх видів об'єктів за допомогою розширення синтаксису списків ініціалізації:

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<SomeType>);), він буде мати вищий пріоритет у порівнянні з іншими можливостями створення об'єктів. Наприклад, в C++0x std::vector містить конструктор, що приймає в якості аргументу список ініціалізації:

std::vector<int> theVec{4};

Цей код призведе до виклику конструктора, що приймає в якості аргументу список ініціалізації, а не конструктора з одним параметром, що створює контейнер заданого розміру. Для виклику цього конструктора користувач повинен буде використовувати стандартний синтаксис виклику конструктора.

Визначення типів[ред.ред. код]

У стандартному C++ (і C) тип змінної повинен бути явно вказаним. Однак, після появи шаблонних типів і технік шаблонного метапрограмування, тип деяких речей, особливо значення котре повертає функція, не може бути легко заданим. Це призводить до складнощів при зберіганні проміжних даних в змінних, іноді може знадобитися знання внутрішньої будови конкретної бібліотеки метапрограмування.

C++0x пропонує два способи для пом'якшення цих проблем. По-перше, визначення явно ініціалізованої змінної може містити ключове слово auto. Це призведе до того, що буде створена змінна з типом, котрий має ініціалізуєче значення:

auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject);
auto otherVariable = 5;

Типом someStrangeCallableType стане той тип, який повертає конкретна реалізація шаблонної функції boost::bind для заданих аргументів. Цей тип легко визначити компіляторові під час виконання семантичного аналізу, а от програмісту для визначення типу довелося б провести ряд досліджень.

Тип otherVariable також чітко визначений, проте, так само легко може бути визначений і програмістом. Цей тип — int, такий же як у цілочислової константи.

Крім того, для визначення типу виразу під час компіляції може бути використано ключове слово decltype. Наприклад:

int someInt;
decltype(someInt) otherIntegerVariable = 5;

Використання decltype найкорисніше спільно з auto, тому що тип змінної, описаної як auto, відомий лише компілятору. Крім того, використання decltype може бути дуже корисним у виразах, які використовують перевантаження операторів та спеціалізацію шаблонів.

auto також може бути використаний для зменшення надлишковості коду. Наприклад, замість:

for (vector<int>::const_iterator itr = myvec.begin(); itr!= myvec.end(); ++itr)

програміст зможе написати:

for(auto itr = myvec.begin(); itr!= Myvec.end(); ++itr)

Різниця стає особливо помітною, коли програміст використовує велику кількість різних контейнерів, не зважаючи на те, що і зараз існує добрий шлях для зменшення надлишковості коду — використання typedef

Тип, позначений як decltype, може відрізнятися від типу виведеного за допомогою auto.

#include<vector>
int main ()
{
 const std::vector<int> v(1);
 auto a = v [0];         // тип a - int 
 decltype (v [0]) b = 1; // тип b - const int&; (значення, яке повертає
                         // std::vector <int>::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++0x вводить подібний засіб. Інструкція 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 — інструкцією з колекції.

Лямбда-функції і вирази[ред.ред. код]

Альтернативний синтаксис функцій[ред.ред. код]

Поліпшення конструкторів об'єктів[ред.ред. код]

Явне перевантаження віртуальних функцій[ред.ред. код]

Константа для нульового вказівника[ред.ред. код]

Строго типізовані перечислення[ред.ред. код]

Кутові дужки[ред.ред. код]

Локальні і безіменні типи як аргументи шаблонів[ред.ред. код]

Явні перетворення операторів[ред.ред. код]

Символи і рядки в Юнікод[ред.ред. код]

«Сирі» рядки (Raw string literals)[ред.ред. код]

Статична діагностика[ред.ред. код]

Template typedefs[ред.ред. код]

Шаблони зі змінною кількістю аргументів[ред.ред. код]

Прибирання сміття[ред.ред. код]

Необмежені об'єднання[ред.ред. код]

Покращення у функціональності ядра[ред.ред. код]

Примітки[ред.ред. код]

  1. а б The C++ Source Bjarne Stroustrup (January 2, 2006) A Brief Look at C++0x. (англ.)
  2. [4] ^ C/C++ Users Journal Bjarne Stroustrup (May, 2005) The Design of C++0x: Reinforcing C++ 's proven strengths, while moving into the future. (англ.)

Посилання[ред.ред. код]