Що нового у С++20: можливості та перспективи

Стаття написана у співавторстві з Андрієм Приступою та Віктором Дикуном — GlobalLogic Technology Management.

Мене звати Олександр Гайсін, я Senior Manager, Engineering GlobalLogic Lviv та працюю над проєктами, що пов’язані з мовою програмування С++. Разом з Андрієм та Віктором, які очолюють напрямок розвитку С++ у львівській локації компанії GlobalLogic, вирішили розповісти про переваги цієї мови та можливості, які відкриває перед розробниками її новий стандарт С++ 20.

Трохи історії, або як з’явилася С++

Мову програмування С++ ще на початку 1980-х років створив Б’ярн Страуструп — співробітник компанії Bell Laboratories. Річ у тому, що мова С, з якою він працював, мала недосконалості. У ній бракувало високорівневих абстрактних типів даних і об’єктів, обробки винятків, а тому техніка реалізації завдання часто домінувала над її змістом. Страуструп вдосконалив мову С для своїх потреб і додав можливість роботи з класами та об’єктами. Покращена версія ґрунтувалася на синтаксисі С, тому її називали «Сі з класами». Хоча нову мову використовували з початку 80-х, її запуск як окремої не планували. Проте 1983 року «Сі з класами» перейменували на «мову програмування C++».

Механізм класів дав можливість визначати і використовувати нові типи даних. А головною метою створення C++ було прагнення збільшити відсоток повторного використання вже написаного коду. Пізніше до С++ додали нові можливості, такі як віртуальні функції, перевантаження функцій і операторів, повноцінний механізм структурної обробки винятків і багато іншого.

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

Еволюція С++ та чому вона потрібна новачкам, і не тільки

З кожним новим стандартом ми отримуємо додаткові функціональні можливості. Згадуємо давні проєкти компанії, коли використовувались самописні структури даних та власноруч створені обгортки, які давали змогу спростити роботу з Operating System dependent API. Їх було складно підтримувати, а спеціалісти, які працювали на них, були практично незамінними.

С++ значно розвинулась за останні 10 років. Ми отримали покращені стандарти С++11, С++14, С++17, а цього року — С++20. Головні переваги, що стали доступні в кожній редакції, можна подивитись на малюнку вище. Такий розвиток мови дозволяє нам уніфікувати код і зробити його більш зрозумілим і доступним для широкого кола розробників.

Якщо раніше для визначення рівня С++ розробника бралось до уваги знання синтаксису і конструкцій мови, то зараз очікування дещо виросли і передбачають знання С++11, С++14, С++17 можливостей, таких як move-семантика, лямбда-функції, об’єкти синхронізації, у тому числі std::future, std::promise, std::condition_variable та інші.

Те саме відбувається зі знанням алгоритмів на базі С++. Якщо раніше інтерв’юери концентрувалися на базових структурах даних, як list чи vector, то з появою нових стандартів очікується, що інженер буде знайомим з unordered_map чи unordered_set і зможе пояснити, яка роль hash-функцій у цих контейнерах.

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

Тому, якщо є можливість використання нового стандарту, команда його використовуватиме.

Таким чином, С++ є досить актуальною мовою вже 35 років та входить до топ-5 мов програмування, оскільки знання С++ дає розуміння, як працює система на низькому рівні. Водночас, мова досить складна і не дарма використовується для системного програмування. Більшість веббраузерів, операційних систем, середовищ розробки чи графічних редакторів створено з використанням С/С++.

При цьому С++ актуальна й для новачків. Наприклад, в основі популярних Java та JavaScript закладені принципи C++. Тому принципи роботи тієї ж Java достатньо непросто опанувати, якщо не знайомий з плюсами. Саме через це С++ вже довгий час залишається однією з основних мов навчання. Так, за даними дослідження GlobalLogic у Львові, 30% студентів визначили основним напрямком саме С++.

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

Стандарт С++ не описує способи іменування об’єктів, деякі деталі обробки винятків та інші можливості, пов’язані з деталями реалізації, що робить несумісним об’єктний код, створений різними компіляторами. Проте (за станом на час написання цієї статті) серед компіляторів С++ все ще продовжується битва за повну реалізацію стандарту С++, особливо в області шаблонів — частини мови, зовсім нещодавно повністю розробленої комітетом стандартизації.

Однією з перешкод у цьому питанні є ключове слово export, що використовується також і для розділення оголошення і визначення шаблонів.

Першим компілятором, що підтримав export у шаблонах, став Comeau C++ на початку 2003 року (за п’ять років після виходу стандарту). 2004 року бета-версія компілятора Borland C++ Builder X також почала його підтримку.

Ці обидва компілятори ґрунтуються на зовнішньому інтерфейсі EDG. Інші компілятори, такі як Microsoft Visual C++ або GCC, взагалі цього не підтримують. Герб Саттер, секретар комітету зі стандартизації С++, рекомендував прибрати export з майбутніх версій стандарту через серйозні труднощі з повноцінною реалізацією, проте згодом остаточним рішенням стало його залишити.

С++ як можливість створювати дійсно круті проєкти

Попри те що у мови С++ багато скептиків, вона буде актуальною ще багато років. С++ використовують у різних технологічних рішеннях, які відомі на весь світ. Наприклад, операційні системи Windows та macOS мають елементи, написані на С++. А YouTube використовує С++ для обробки відео. Крім цього, С++ потрібна для розробок вбудованих систем (embedded). Це різноманітні пристрої, від смартфонів до інсулінової помпи і бортового комп’ютера автомобіля.

Крім того, цю мову використовує й компанія SpaceX для створення своїх програмних рішень.

Згідно з результатами внутрішнього дослідження у межах львівської локації GlobaLlogic, мова С++ є основною для таких проєктів:

  • Розробка сервера для аудіо/відеоконференцій.
  • Автомобільна промисловість — С++ потрібна, щоб пов’язати залізо з програмним забезпеченням. Наприклад, бортовий комп’ютер.
  • Розумні будинки.
  • Медіаплеєри.
  • Системи моніторингу (від відеонянь до операційних).

Тому спеціалісти з С++ створюють справді круті проєкти, які змінюють світ на краще та якісно покращують життя людей.

Варто підкреслити, що C++20 змінить спосіб програмування C++ так само принципово, як і C++11. Це справедливо, зокрема, для великої четвірки: «Ranges, Coroutines, Concepts, and Modules». Півроку тому можна було казати навіть про велику п’ятірку, але Contracts були скасовані на засіданні зі стандартизації цього року в Кельні. Для довідки, Contracts точно і чітко визначають інтерфейси програмних компонентів. Але це в минулому, адже краще дивитись у світле блискуче майбутнє, яке настає вже зараз.

Можливості нової С++20

4 вересня 2020 року комітет ISO зі стандартизації С++ затвердив новий стандарт мови — С++20. Затверджені зміни найбільш вплинуть на загальну екосистему мови із часів С++11, а можливо, і за всю історію С++. Тобто новий стандарт не лише додає нові можливості, а й фундаментально змінює підходи до написання коду, розуміння функцій, створення шаблонів, проєктування бібліотек, організацію пакетів та компіляцію С++ програм.

Так, нова бібліотека діапазонів дозволяє їй виражати алгоритми безпосередньо на контейнері, складати алгоритм із «pipe symbol» та застосовувати їх до нелімітовано довгих потоків даних.

Завдяки Coroutines асинхронне програмування на C++ може стати основним. Coroutines — це основа для спільних завдань, циклів подій, нескінченних потоків даних або конвеєрів.

Нова концепція змінить спосіб мислення та шаблони програмування. Вони є семантичними категоріями для вірних аргументів шаблону. Вони дозволяють вам висловити свій намір безпосередньо в системі типів. Якщо щось піде не так, ви отримаєте коротке повідомлення про помилку.

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

Але це далеко не все. Ось кілька додаткових моментів:

  • тристоронній оператор порівняння <=>
  • рядкові літерали як параметри шаблону
  • розширення календаря та часового поясу бібліотеки «chrono»
  • std :: span як подання на суміжний масив
  • віртуальна функція constexpr
  • перевизначення volatile
  • доповнення до: std :: atomic_ref та std :: atomic >; включено можливість очікування завершення операції
  • нові механізми синхронізації, такі як semaphores, latch та barriers
  • контейнери constexpr
  • вдосконалений потік std :: jthread, який автоматично приєднується і може бути зупинений

Детальніше можна подивитись на малюнку нижче:

Втім, не варто хвилюватись, ті частини, які дійсно зазначені як volatile, не зміняться.

Щодо підтримки нових можливостей стандарту С++20 різноманітними компіляторами можна подивитись на таблицю нижче та дізнатися більше деталей.

Висновки

Загалом, до С++ додалось багато нових функцій, деякі з яких, безсумнівно, гідні того, аби радіти.

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

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

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

Стежимо за тим, що відбувається в комітеті зі стандартизації С++.

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

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

👍НравитсяПонравилось8
В избранноеВ избранном2
Подписаться на автора
LinkedIn



Підписуйтесь: Soundcloud | Google Podcast | YouTube


21 комментарий

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

По-моему плюсам пора на пенсию. Да и трудовой стаж уже подходящий.

повноцінний механізм структурної обробки винятків і багато іншого.

но finally так и не завезли ))

1990 год, заседание ISO по стандартизации C++

— Давайте запилим исключения в стандарте С++?
— Точно. Не забудь еще finally добавить.
— Подожди, куда торопиться? Если finally добавим сейчас, что что мы будем в стандарт С++ 25 добавлять?
— Правильно мыслишь!

Там вроде не в finally дело а в stack unwinding в целом. По слухам Google предлогает (демонстрирует прототип) поменять сами механизмы что должно привести к 20% приросту производительности генерируемого кода. Но это разломает существующие код бейсы, потому обсуждение перенесли с 23 на 25 стандарт. А вызов деструкторов локальных объектов при выбросе исключения никто не отменял, поэтому по идее finally интересен только как синтаксический сахар.

В языке, где есть RAII, finally не особо нужен. Может, конечно, помочь свитчерам из других языков чувствовать себя комфортнее, чисто как синтаксический сахар. Но в целом плюсовики уже давно привыкли для этих целей использовать деструкторы или вспомогательные штуки типа александрескиного SCOPE_EXIT. Что является гораздо более универсальным подходом, работающим в большем количестве сценариев, чем код с try-блоками.

В языке, где есть RAII, finally не особо нужен.

youtu.be/u-54OpEwE0w

ЗЫ: обрати внимание в SEH он есть но как результат того что в си++ его нет вся эта кутерьма «супер гайдов по написанию exception safe кода» доступных в лучшех случае самому тов. александреску и ещё нескольким экспертам если они соизволят всё это ещё раз перечитывать плюс эти пляски с noexcept и отдельно с nothrow об котором знают ну совсем единицы зато можно на конкурсах валить ))

вся эта кутерьма «супер гайдов по написанию exception safe кода» доступных в лучшех случае самому тов. александреску и ещё нескольким экспертам

На каждом заборе все эти гайды написаны, их должен знать любой программист уровня миддла или выше, если он работает с кодом, в котором в принципе эксепшены включены (если нет — научится быстро, ничего такого уж страшного там нет, чисто дело привычки).

пляски с noexcept

Да нет там никаких плясок. Знаешь что твоя функция никак не может бросать эксепшены — пишешь noexcept. Тулить его везде по любому поводу не обязательно, главное мувающие операции им обозначать.

nothrow об котором знают ну совсем единицы

Если ты про throw() в декларациях функций, то он уже давно deprecated и не используется. В C++20 его окончательно ремувнули из стандарта кстати.
Если про std::nothrow, то тот, кому он надо, без труда найдёт и разберётся, как пользоваться. Изначально программист должен просто знать, что оператор new в случае фейла бросает эксепшен. Задастся вопросом «а что, если я хочу получить nullptr?» — найдёт nothrow.
Если ты про type traits со словом nothrow, то они нужны только экспертам, пишущим дженерик библиотеки. Простым смертным вообще об этом задумываться не нужно.

Використовую github.com/SergiusTheBest/ScopeExit — зручніше за finally.

О, це те про що я говорив :)
Я теж подібне навелосипедив і юзаємо в команді, нам норм.

Дуже радий, що в свій час ввів в примусовому порядку на попередньому проекті під QNX «-fno-exceptions».

На поточному під Linux пішли трохи іншим шляхом — ловимо SIGSEGV, SIGABRT, і зупиняємо тільки той потік, в якому це виникло, поки інші працюють. А далі діагностика вищого рівня.

Для справедливості, треба додати оригінал статті: www.modernescpp.com/...​ndex.php/c-20-an-overview ; )

Модулі подолають обмеження файлів заголовків, що є дуже перспективним. Наприклад, розділення файлів заголовків та вихідних файлів стає застарілим, так само, як і препроцесори.

Препроцесор нікуди не дівається, модулі його не заміняють повністю. Всілякі там #ifdef та #pragma будуть використовувати препроцесор і надалі.

як тепер чудово виглядає код на сучасному с++ (хоча це вже з 17 є):

void f(int n) {
  void g(), h(), i();
  switch (n) {
    case 1:
    case 2:
      g();
     [[fallthrough]];
    case 3: // no warning on fallthrough
      h();
    case 4: // compiler may warn on fallthrough
      if(n < 3) {
          i();
          [[fallthrough]]; // OK
      }
      else {
          return;
      }
    case 5:
      while (false) {
        [[fallthrough]]; // ill-formed: next statement is not part of the same iteration
      }
    case 6:
      [[fallthrough]]; // ill-formed, no subsequent case or default label
  }
}

3 останні приклади застосування [[fallthrough]] — надумані. Перший може і справді знадобитися щоб «погасити» непотрібний ворнінг.

звичайно що «надумані», це просто приклад з cppreference.com взяв, не придумувати ж його.
тут не в самому тому [[fallthrough]] чужим в коді. таке собі «покращення».

Такі приклади потрібно сприймати як демонстрацію того, які використання атрибуту скомпіляться, які ні і чому. А не як приклад реального коду, який начебто хтось рекомендує писати в реальному житті.

У всіх випадках, де я бачив (і сам використовував) [[fallthrough]], він ставився виключно перед наступним «case ... :», щоб явно сказати компілятору (а заодно й іншим програмістам, які читатимуть цей код), що в даному місці break пропустили спеціально і це не є помилкою.

яка красота.. через те що хтось вирішив що два об’єднаних кейса (а це поширена практика) це підозріло і треба warrning видати, а в багатьох перфекціоністів warrning==error — доводиться додавати в нормальний код атрібут який там виглядає як сідло на свині

Красота суб’єктивна. Не подобається — можете на рівні компілятора відімкнути цей ворнінг і не паритися про використання атрибуту, який у вас викликає настільки бурхливу реакцію.

І ні: два об’єднаних кейса у тому контексті, в якому потрібен атрибут [[fallthrough]], — це не поширена практика. Розберіться, про що йде мова, перш ніж так емоційно хейтити.

Придивіться до прикладу уважніше. Між «case 1:» та «case 2:» жодних fallthrough не потрібно, там все тривіально і очевидно, бо ці кейси дійсно повноцінно об’єднані. Між ними немає жодних дій, які були б «ексклюзивними» для кейса 1 і не розповсюджувалися на кейс 2. Ось це і є нормальне типове об’єднання кейсів. В таких випадках все ще не потрібно ніяких додаткових дій. Атрибут задумувався не для цього.

А от кейси 2 і 3 без явного fallthrough виглядають стрьомно, тому що не очевидно, чи малося на увазі в кейсі 2 викликати лише g(), чи кейс 2 справді повинен послідовно викликати g() і h().
В switch-case конструкціях це була (і є) достатньо поширена бага, коли break тупо забувався через неуважність програміста зокрема та специфічність синтаксису switch-case в цілому.
Тому введення спеціального синтаксису для позначення явного наміру переходити у наступний case для таких випадків (які зазвичай зустрічаються досить рідко) — штука корисна.

ЗІ
В реальному коді — а не штучних прикладах, де в кожному кейсі лише один рядок, який викликає одну функцію, назва якої складаєься з однієї літери, — атрибут [[fallthrough]] і близько не виглядає так погано, як може здаватися при розгляданні подібних прикладів.

Епрст, как оно тут все изменилось 0_0

Где? Вроде ж ничего нового, кроме атрибута [[fallthrough]]. Который, как сказали выше, уместен только в сценариях вроде первого в этом примере.
Во втором сценарии его следовало бы поставить после «else { return; }», а не там, где он стоит сейчас. Третий и четвёртый вообще не имеют смысла.

Хоча підтримка нетворкінгу не увійшла до С++20

Скоріше радійте, довелось довго протестувати проти дизайну boost asio який тягнули в стандарт за вуха. Було визнано що в цьому вигляді цей API жахливий. І автори почали працювали над переробкою API.

Подписаться на комментарии