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++11 пропонує два способи для пом'якшення цих проблем. По-перше, визначення явно ініціалізованої змінної може містити ключове слово 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 — інструкцією з колекції.

Лямбда-функції і вирази

Одним з найважливіших нововведень С++11 є лямбда функції. Лямбда-функція визначається наступним чином :

[](int x, int y) { return x + y; }

Детальніше в статті Лямбда-вирази у С++

Альтернативний синтаксис функцій

Синтаксис оголошення функцій у стандарті C чудово відповідав набору характеристик мови. Із розвитком C++ від C базовий синтаксис залишався і розширювався за необхідності. Однак, з тим як C++ ставав усе складнішим, виявились низка обмежень, особливо щодо оголошення шаблонних функцій. Наступний приклад не дозволений у C++03:

template<class Lhs, class Rhs>
  Ret adding_func(const Lhs &lhs, const Rhs &rhs) {return lhs + rhs;} //Ret мусить бути типом lhs+rhs

Тип Ret є будь-чим, у що виллється додавання типів Lhs і Rhs. Навіть з попередньо згаданою функціональністю C++11 decltype, це не можливо:

template<class Lhs, class Rhs>
  decltype(lhs+rhs) adding_func(const Lhs &lhs, const Rhs &rhs) {return lhs + rhs;} //Не правильно у C++11

Це не правильно у C++, бо lhs і rhs ще не визначені; вони не стануть дійсними ідентифікаторами допоки парсер не розбере залишок прототипу функції.

Для обходження цього, C++11 вводить новий синтаксис для оголошення функцій із хвостовим-типом-повернення (англ. trailing-return-type):

template<class Lhs, class Rhs>
  auto adding_func(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}

Цей синтаксис можна використати і для більш земних оголошень і визначень функцій:

struct SomeStruct  {
    auto func_name(int x, int y) -> int;
};

auto SomeStruct::func_name(int x, int y) -> int {
    return x + y;
}

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

У C++03, конструкторам класу не дозволяється викликати інші конструктори цього класу; кожен конструктор повинен конструювати всі члени класу самостійно або викликати функції члени, як-от,

class SomeType  {
    int number;

private:
    void Construct(int new_number) { number = new_number; }
public:
    SomeType(int new_number) { Construct(new_number); }
    SomeType() { Construct(42); }
};

Конструктори базового класу не можуть бути прямо відкриті підкласам; кожен підклас мусить реалізувати конструктори навіть якщо конструктор базового класу підійшов би. Несталі члени даних класу не можна ініціалізувати у місці оголошення цих членів. Їх можна ініціалізувати лише у конструкторі.

C++11 забезпечує вирішення цим проблемам.

C++11 дозволяє конструкторам викликати інші конструктори цього класу (відомо як делегування). Це дозволяє конструкторам використовувати поведінку інших конструкторів з найменшим додатковим кодом. Делегування використовували інші мови, наприклад Java.

Синтаксис такий:

class SomeType  {
    int number;

public:
    SomeType(int new_number) : number(new_number) {}
    SomeType() : SomeType(42) {}
};

Зауважте, що у цьому випадку, той самий ефект можна досягти зробивши new_number параметром зі значенням за умовчанням. Однак, новий синтаксис дозволяє виразити значення за умовчанням (42) у реалізації радше ніж в інтерфейсі - перевага для супроводу коду бібліотек оскільки значення за умовчанням «запечені у» місцях виклику, натомість делегування конструкторів дозволяє змінювати це значення без необхідності перекомпілювання коду, що використовує бібліотеку.

Тут важливе застереження: C++03 вважає об'єкт сконструйованим коли його конструктор завершив виконання, але C++11 вважає об'єкт сконструйованим щойно будь-який конструктор завершив виконання. Оскільки буде дозволене виконання багатьох конструкторів, то конструктор, що делегує виконуватиметься на вже сконструйованому об'єкті. Конструктори породжених класів виконуватимуться після того як всі делегування у базових класах завершаться.

Для конструкторів базового класу, C++11 дозволяє визначити їх успадкування. Це значить, що компілятор C++11 згенерує код для успадкування, переадресації підкласу до базового класу. Зауважте, що це все-або-нічого можливість; або переадресовуються всі конструктори базового класу або жоден. Також зауважте, що існує обмеження на множинне спадкування, таке що конструктори класу не можна успадкувати від двох класів які використовують конструктори з однаковими сигнатурами. Також не може існувати конструктор підкласу який має сигнатуру як у конструктора у базовому класі.

Синтаксис такий:

class BaseClass {
public:
    BaseClass(int value);
};

class DerivedClass : public BaseClass {
public:
    using BaseClass::BaseClass;
};

Для ініціалізації членів C++11 надає такий синтаксис:

class SomeClass {
public:
    SomeClass() {}
    explicit SomeClass(int new_value) : value(new_value) {}

private:
    int value = 5;
};

Будь-який конструктор класу ініціалізує value у 5, якщо конструктор сам не перевизначає ініціалізацію. Отже вище, порожній конструктор ініціалізує value як воно визначено у визначенні класу, тоді як конструктор, що приймає цілочисельний параметр ініціалізує його значенням цього параметра.

Також value можна ініціалізувати у конструкторі або через однорідну ініціалізацію, наприклад так:

explicit SomeClass(int new_value) { value = new_value; }

Або так:

explicit SomeClass(int new_value) : value{ new_value } {}

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

У C++03 можливо випадково створити нову віртуальну функцію замість заміни функції базового класу. Наприклад:

struct Base {
    virtual void some_func(float);
};

struct Derived : Base {
    virtual void some_func(int);
};

Припустимо Derived::some_func мало на меті заміну версії базового класу. Але натомість, оскільки функція базового класу має іншу сигнатуру, створено нову віртуальну функцію. Це звичайна проблема, особливо якщо користувач змінив базовий клас.

C++11 надає синтаксис, щоб вирішити цю проблему.

struct Base {
    virtual void some_func(float);
};

struct Derived : Base {
    virtual void some_func(int) override; // неправильно - не заміняє метод базового класу
};

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

C++11 також додає можливість, щоб запобігти успадкуванню від класу або просто запобігти заміні функції у підкласах. Це робиться за допомогою ідентифікатора final. Наприклад:

struct Base1 final { };

struct Derived1 : Base1 { }; // неправильно, бо клас Base1 позначений фінальним
struct Base2 {
    virtual void f() final;
};

struct Derived2 : Base2 {
    void f(); // неправильно, бо віртуальна функція Base2::f позначена фінальною
};

У цьому прикладі, virtual void f() final; оголошує віртуальну функцію, і також забороняє підкласам заміняти її.

Зауважте, що ані override, ані final не є ключовими словами мови. Технічно, вони ідентифікатори для атрибутів визначення функцій і класів:

  • вони набувають особливого значення як атрибути лише коли використані у цьому специфічному хвостовому контексті;
  • вони не змінюють сигнатуру типу і не оголошують або заміняють будь-який новий ідентифікатор у будь-якій зоні видності;
  • їх можна використати як допустимий ідентифікатор для нових оголошень (наприклад, змінних).

Константа для нульового вказівника

З часів появи C в 1972 р., константа 0 відігравала подвійну роль цілого числа і нульового вказівника. Одним зі способів боротьби з цією невизначеністю, властивою мові C, є макрос NULL, який як правило здійснює підстановку ((void*)0) або 0. C++ в цьому плані відрізняється від C, дозволяючи використовувати тільки 0 як константу нульового вказівника. Це призводить до поганої взаємодії з перевантаженням функцій:

void foo(char *);
void foo(int);

Якщо макрос NULL визначений як 0 (що є звичним для C++), рядок foo(NULL); призведе до виклику foo(int), а не foo(char *), як можна припустити при поверхневому перегляді коду, що майже напевно не збігається з планами програміста.

Однією з новинок C++11 є нове ключове слово для опису константи нульового вказівника nullptr. Дана константа має тип std::nullptr_t, який можна неявно конвертувати в тип будь-якого вказівника і порівняти з будь-яким вказівником. Неявна конвертація в цілочисельний тип недопустима, за винятком bool. У вихідній пропозиції стандарту не допускалося неявної конвертації в булевий тип, але робоча група розробників стандарту дозволила таке перетворення з метою сумісності зі звичайними типами вказівників. Запропоноване формулювання було змінено після одноголосного голосування у червні 2008 року.

З метою забезпечення оберненої сумісності, константа 0 також може використовуватися як нульовий вказівник.

char *pc = nullptr;     // правильно
int  *pi = nullptr;     // правильно
bool   b = nullptr;     // правильно. b = false.
int    i = nullptr;     // помилка

foo(nullptr);           // викликає foo(char *), а не foo(int);

Строго типізовані перечислення

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

C++11 пропонує спеціальну класифікацію цих перечислень, вільну від вищеописаних недоліків. Для опису таких перечислень використовується оголошення enum class (також можливим є використання enum struct як синоніма):

enum class Enumeration {
    Val1,
    Val2,
    Val3 = 100,
    Val4, /* = 101 */
};

Таке перечислення є типобезпечним. Елементи класового перечислення неможливо неявно перетворити в цілі числа. Як наслідок, порівняння з цілими числами також є неможливим (вираз Enumeration::Val4 == 101 викликає помилку компіляції).

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

enum class Enum2 : unsigned int {Val1, Val2};

Область дії елементів перечислень визначається областю дії імені перечислення. Використання імен елементів потребує вказування імені класового перечислення. Так, наприклад, значення Enum2::Val1 визначене, а значення Val1 — не визначене.

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

enum Enum3 : unsigned long {Val1 = 1, Val2};

В даному прикладі імена елементів перечислення визначені в просторі перечислення (Enum3::Val1), але для забезпечення оберненої сумісності імена елементів також доступні в загальній області видимості.

Також в C++11 можливе попереднє оголошення перечислень. В попередніх версіях C++ це було неможливим, оскільки розмір перечислення залежав від його елементів. Також оголошення можна використовувати тільки в тих випадках, коли розмір перечислення вказаний (явно або неявно):

enum Enum1;                   // неправильно для C++ і C++11; базовий тип не може бути визначений
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, оскільки Enum2 раніше оголошений з іншим базовим типом

Кутові дужки

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

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;".

Як було показано вище, дана зміна не зовсім сумісна з попереднім стандартом.

«Сирі» рядки (Raw string literals)

Сирий рядок - це рядок, в якому не діють спеціальні символи типу '\n', '\b' і тд., а кожен символ представляє сам себе.

Для створення сирого рядка використовують запис R"(Hello, World!)", потрібний рядок пишеться в дужках - тут рядком є "Hello, World!".

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

Необмежені об'єднання

У C++03, існує обмеження на те які типи об'єктів могли бути членами union. Наприклад, об'єднання не могли містити будь-яких об'єктів, що мали нетривіальні конструктори або деструктори. C++11 знімає деякі з цих обмежень.

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

Ось простий приклад об'єднання дозволеного у C++11:

#include <new> // Необхідний для 'new' зі вказанням розташування.

struct Point {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {} 
    int x_, y_;
};

union U {
    int z;
    double w;
    Point p; // Illegal in C++03; legal in C++11.
    U() {new(&p) Point();} // Через член типу Point, вимагається визначення конструктора.
};

Ці зміни не зламають жодного написаного коду оскільки вони лише полегшують правила прийняті у C++03.

Примітки

  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. (англ.)

Документи комітету по стандартизації C++

  •   Doc No. 2697: ISO/IEC DTR 19768 (June 15, 2008) Minutes of WG21 Meeting June 8-15, 2008

Посилання

This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.