C++26 поч­ти утвер­жден — новые фичи боль­ше не добав­ляют­ся, так что пора изу­чить, что в ито­ге получи­лось. Мы раз­берем клю­чевые воз­можнос­ти стан­дарта и про­тес­тиру­ем их в деле. Заод­но узна­ем, какие ком­пилято­ры и инс­тру­мен­ты уже под­держи­вают новин­ку. Даже если ты не собира­ешь­ся сра­зу перехо­дить на новый стан­дарт, зна­комс­тво с его нов­шес­тва­ми полез­но: они вли­яют на стиль, про­изво­дитель­ность и будущее всей эко­сис­темы C++.

C++ — успешный пре­емник язы­ка C, который про­ник во все сфе­ры, где есть информа­цион­ные тех­нологии, то есть бук­валь­но вез­де. Даже если зав­тра C++ заменит какой‑нибудь новомод­ный язык вро­де Rust, Zig или Nim, под­дер­жка все­го соф­та на C/C++ оста­нет­ся акту­аль­ной.

Час­то новые вер­сии C++ заимс­тву­ют фичи у новомод­ных язы­ков вро­де перечис­ленных выше. Нап­ример, модули вмес­то заголо­воч­ных фай­лов, тип std::optional, сис­тема вла­дения (в виде умных ука­зате­лей) и мно­гопо­точ­ные абс­трак­ции заимс­тво­ваны из Rust. Лям­бды — из Lisp. C++ так­же перенял от Lisp и интер­пре­тиру­емых язы­ков, таких как Python и JavaScript, динами­чес­кую типиза­цию, которая выраже­на в std::any и std::variant. Мне кажет­ся, что заимс­тво­вание удач­ных и про­верен­ных решений не толь­ко уско­ряет раз­витие язы­ков, но и позитив­но ска­зыва­ется на выборе областей при­мене­ния.

21 июня 2025 года комитет ISO C++ завер­шил работу над про­ектом раз­вития язы­ка C++26. Его спе­цифи­кация готова: в него вклю­чены все вос­тре­бован­ные фун­кции, и изме­нений боль­ше не будет. Теперь раз­работ­чики ком­пилято­ров могут спо­кой­но прис­тупать к реали­зации нового стан­дарта в коде.

info

На утвер­жде­ние докумен­та пот­ратили неделю упор­ной работы 200 спе­циалис­тов на заседа­нии комите­та ISO C++ в Софии, Бол­гария. Боль­шинс­тво при­сутс­тво­вало лич­но, осталь­ные под­клю­чались через Zoom. Все­го в мероп­риятии при­няли учас­тие пред­ста­вите­ли 30 стран. По сло­вам Гер­ба Сат­тера, при­нятый стан­дарт ока­зал­ся боль­ше по объ­ему, чем все пре­дыду­щие за пос­ледние 20 лет.

Но­вый стан­дарт язы­ка C++ выходит каж­дые три года. Одна­ко дей­стви­тель­но новые фичи появ­ляют­ся раз в шесть лет, а про­межу­точ­ные вер­сии — это что‑то вро­де работы над ошиб­ками. Нап­ример, пре­дыду­щий круп­ный выпуск — это C++20, а C++23 стал исправ­лени­ем оши­бок пред­шес­твен­ника. Гря­дущий C++26 сно­ва пред­ложит силь­но обновлен­ный язык.

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

 

Ставим подходящий компилятор

Преж­де чем обсуждать новов­ведения язы­ка, давай раз­берем­ся с тес­товой сре­дой, в которой мож­но будет про­верять эти воз­можнос­ти.

В пос­ледней вер­сии ком­пилято­ра Microsoft Visual C++, который вхо­дит в Visual Studio 2022 (вер­сия 17.14), до сих пор отсутс­тву­ет пол­ная под­дер­жка C++23. Что уж говорить о под­дер­жке C++26!

На выбор оста­ются два дру­гих ком­пилято­ра: GCC и Clang. Оба при­мер­но на две тре­ти под­держи­вают стан­дарт C++26, хотя Clang обыч­но получа­ет обновле­ния рань­ше. Это свя­зано с фун­дамен­таль­ным устрой­ством этих ком­пилято­ров.

 

GCC

GCC — это монолит­ная сис­тема, в которой фрон­тенд, внут­ренний плат­формен­но незави­симый язык и бэкенд работа­ют как еди­ный про­цесс. Фрон­тенд обра­баты­вает высоко­уров­невый язык, выпол­няет его лек­сичес­кий и син­такси­чес­кий ана­лиз и пре­обра­зует его во внут­ренний язык переда­чи регис­тров — RTL. GCC под­держи­вает фрон­тенды для язы­ков C, C++, Objective-C, Fortran и мно­гих дру­гих.

За­тем под­клю­чает­ся под­ходящий бэкенд, который пре­обра­зует код с язы­ка RTL в машин­ный код для выб­ранной архи­тек­туры: x86, x86-64, ARM, MIPS, SPARC, PowerPC и дру­гих. Кста­ти, GCC под­держи­вает наиболь­шее количес­тво бэкен­дов, обес­печивая генера­цию кода для мно­жес­тва архи­тек­тур. Это дела­ет GCC ведущим ком­пилято­ром для поч­ти все­го сис­темно­го ПО. Если твой код ком­пилиру­ется под одну архи­тек­туру, то с боль­шой веро­ятностью он соберет­ся с GCC и под дру­гие.

За­меть, что GCC — это доволь­но ста­рый софт. Ричард Стол­лман выпус­тил его еще в 1987 году, и с тех пор сооб­щес­тво откры­того ПО неп­рерыв­но раз­вива­ет GCC.

 

LLVM

Clang — это фрон­тенд для язы­ков C/C++, Objective-C/C++ внут­ри фрей­мвор­ка LLVM. Хотя LLVM рас­шифро­выва­ется как Low Level Virtual Machine, вир­туаль­ной машиной в при­выч­ном понима­нии он не явля­ется. Ско­рее LLVM напоми­нает плат­форму для раз­работ­ки ком­пилято­ров, похожую на GCC.

Глав­ное отли­чие от GCC — модуль­ная архи­тек­тура: каж­дый слой LLVM — это отдель­ный механизм. Это поз­воля­ет быс­тро вно­сить изме­нения в инфраструк­туру и раз­вивать про­ект, добав­ляя под­дер­жку новых язы­ков прог­рамми­рова­ния (фрон­тендов) и новых аппа­рат­ных архи­тек­тур (бэкен­дов). В качес­тве внут­ренне­го кросс‑плат­формен­ного язы­ка LLVM исполь­зует про­межу­точ­ное пред­став­ление — IR. Бла­года­ря модуль­ной архи­тек­туре LLVM раз­вива­ется быс­трее, чем GCC, и даже вытес­няет его.

info

Раз­работ­ка фрей­мвор­ка LLVM началась в 2000 году в Уни­вер­ситете Илли­ной­са. К середи­не вто­рого десяти­летия XXI века фрей­мворк прив­лек боль­шое вни­мание в IT-отрасли и стал широко исполь­зовать­ся ведущи­ми ком­пани­ями: Apple, Adobe, AMD, Google, Nvidia и дру­гими.

 

Какой компилятор выбрать?

Clang в боль­шинс­тве слу­чаев ком­пилиру­ет быс­трее, чем GCC, бла­года­ря сво­ей модуль­ной архи­тек­туре. Такая струк­тура дела­ет Clang/LLVM иде­аль­ным для соз­дания инс­тру­мен­тов, IDE и спе­циали­зиро­ван­ных ути­лит для кодин­га. Так­же Clang известен более информа­тив­ными сооб­щени­ями об ошиб­ках во вре­мя ком­пиляции по срав­нению с GCC. Одна­ко GCC дает луч­шую опти­миза­цию бла­года­ря мно­жес­тву механиз­мов, реали­зован­ных в виде фла­гов ком­пиляции, таких как -Wall, -Wextra и дру­гие, которые помога­ют выяв­лять потен­циаль­ные проб­лемы.

Нель­зя однознач­но выб­рать один ком­пилятор — все зависит от нужд кон­крет­ного про­екта. Чем боль­ше срав­нива­ешь, тем слож­нее при­нять решение. В macOS я обыч­но поль­зуюсь Clang, так как он стан­дар­тный в Xcode для тран­сля­ции кода на C++. Мне осо­бен­но инте­рес­но, как воз­можнос­ти C++26 будут работать на про­цес­соре семей­ства Apple Silicon. Одна­ко вер­сия ком­пилято­ра C++ по умол­чанию в Xcode для наших целей не под­ходит.

Я не собира­юсь нас­таивать на сво­ем выборе, так что можешь взять любую Unix-подоб­ную сис­тему. Затем пред­лагаю уста­новить пос­леднюю вер­сию Clang и LLVM. Для это­го исполь­зуй Homebrew — этот менед­жер пакетов работа­ет как на macOS, так и в Linux. Но для начала про­верим, какая вер­сия у тебя уже уста­нов­лена.

Пос­коль­ку на моей сис­теме уста­нов­лен Xcode с обыч­ной для него вер­сией Clang, коман­да clang -v, выпол­ненная в коман­дной стро­ке, показы­вает

Apple clang version 17.0.0 (clang-1700.0.13.5)
Target: arm64-apple-darwin24.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

warning

Пе­ред уста­нов­кой LLVM не забудь пос­тавить пос­леднюю вер­сию Python, если он еще не уста­нов­лен. На момент написа­ния статьи акту­аль­ной вер­сией была 3.13.5. Python необ­ходим для уста­нов­ки LLVM.

 

Homebrew

Что­бы уста­новить менед­жер потерян­ных пакетов, выпол­ни в кон­соли такую коман­ду:

/bin/zsh -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"(https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)")

Она сра­бота­ет незави­симо от того, исполь­зуешь ты Linux или macOS.

Сайт Homebrew
Сайт Homebrew

Ус­тановим све­жую вер­сию Clang/LLVM через Homebrew:

brew install llvm

По умол­чанию будет уста­нов­лена пос­ледняя дос­тупная вер­сия 20.1.8. Ее номер не свя­зан с вер­сией стан­дарта язы­ка, так как у Clang/LLVM своя сис­тема нумера­ции. Это имен­но то, что нуж­но.

Де­лаем уста­нов­ленный ком­пилятор пер­вым в спис­ке (в перемен­ной сре­ды):

echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc

При­меня­ем нас­трой­ки:

source ~/.zshrc

Вновь выпол­няем:

clang –v

Смот­рим резуль­тат:

Homebrew clang version 20.1.8
Target: arm64-apple-darwin24.5.0
Thread model: posix
InstalledDir: /opt/homebrew/Cellar/llvm/20.1.8/bin
Configuration file: /opt/homebrew/etc/clang/arm64-apple-darwin24.cfg
System configuration file directory: /opt/homebrew/etc/clang
User configuration file directory: /Users/yurembo/.config/clang
 

Дополнительные настройки компилятора

Для нового ком­пилято­ра нуж­но пере­опре­делить пути к катало­гам заголо­воч­ных и объ­ектных фай­лов. Ина­че сис­тема будет исполь­зовать их из катало­га по умол­чанию:

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

Что­бы не про­писы­вать каж­дую строч­ку вруч­ную через коман­дную стро­ку, открой кон­фиг в любом тек­сто­вом редак­торе (нап­ример, в VS Code). Для это­го, находясь в домаш­нем катало­ге, выпол­ни

code .zshrc

Вставь в открыв­ший­ся файл две стро­ки, при­веден­ные ниже. Они ука­зыва­ют на катало­ги вклю­чаемых и объ­ектных фай­лов для недав­но уста­нов­ленно­го ком­пилято­ра:

export LDFLAGS="-L/opt/homebrew/opt/llvm/lib"
export CPPFLAGS="-I/opt/homebrew/opt/llvm/include"
Строки, указывающие на каталоги включаемых и объектных файлов, для нового компилятора
Стро­ки, ука­зыва­ющие на катало­ги вклю­чаемых и объ­ектных фай­лов, для нового ком­пилято­ра

Те­перь по умол­чанию будет исполь­зовать­ся новый ком­пилятор. Воз­ника­ет воп­рос: почему Apple не интегри­рует его в свою сре­ду раз­работ­ки сра­зу? Ответ прост: что­бы защитить поль­зовате­лей от воз­можных багов и недора­ботан­ных фун­кций.

info

Ско­рее все­го, тебе понадо­бит­ся ком­понов­щик lld. Рань­ше он уста­нав­ливал­ся вмес­те с LLM, но в новых вер­сиях его нуж­но уста­нав­ливать отдель­но коман­дой brew install lld.

Нач­нем с про­вер­ки работос­пособ­ности нового ком­пилято­ра, ском­пилиро­вав прог­рамму из коман­дной стро­ки. Для при­мера возь­мем прос­той код, демонс­три­рующий работу клас­са std::jthread, добав­ленно­го в C++20. Глав­ное отли­чие std::jthread от std::thread из C++11 в том, что jthread поз­воля­ет оста­нав­ливать поток в любое вре­мя, не дожида­ясь завер­шения выпол­нения свя­зан­ного с ним алго­рит­ма.

#include <chrono>
#include <iostream>
#include <thread>
using namespace std::literals;
int main() {
std::cout << '\n';
std::jthread nonInterruptible([]{
int counter{0};
while (counter < 10){
std::this_thread::sleep_for(0.2s);
std::cerr << "nonInterruptible: " << counter << '\n';
++counter;
}
});
std::jthread interruptible([](std::stop_token stoken){
int counter{0};
while (counter < 10){
std::this_thread::sleep_for(0.2s);
if (stoken.stop_requested()) return;
std::cerr << "interruptible: " << counter << '\n';
++counter;
}
});
std::this_thread::sleep_for(1s);
std::cerr << '\n';
std::cerr << "Main thread interrupts both jthreads" << '\n';
nonInterruptible.request_stop();
interruptible.request_stop();
std::cout << '\n';
}

Сох­раним и ском­пилиру­ем этот файл:

clang++ -std=c++26 jthread.cpp -o jthread

Ес­ли ты пра­виль­но выпол­нил все шаги по уста­нов­ке и нас­трой­ке, прог­рамма успешно ском­пилиру­ется. Запус­ти ее коман­дой ./jthread.

Прог­рамма соз­дает и сра­зу запус­кает через лям­бда‑фун­кции, исполь­зуя класс std::jthread, два потока: nonInterruptible и interruptible. Вто­рой поток мож­но прер­вать по зап­росу. Оба потока выпол­няют схо­жий алго­ритм: каж­дый запус­кает цикл, который завер­шает­ся пос­ле десяти ите­раций. В начале цик­ла поток засыпа­ет на 200 мс, затем про­сыпа­ется и выпол­няет две или четыре опе­рации в зависи­мос­ти от наз­начения.

Пре­рыва­емый поток про­веря­ет, получен ли параметр std::stop_token с зап­росом stop_requested, и, если это так, завер­шает свою работу. Две общие опе­рации для обо­их потоков: вывод наз­вания потока вмес­те со зна­чени­ем счет­чика и уве­личе­ние самого счет­чика.

Пос­ле запус­ка потоков основная прог­рамма засыпа­ет на одну секун­ду; в это вре­мя потоки про­дол­жают работать. Затем она выводит тек­сто­вую стро­ку и отправ­ляет обо­им потокам зап­росы на оста­нов­ку, но толь­ко пре­рыва­емый поток реаги­рует на это и завер­шает выпол­нение, в то вре­мя как неп­рерыва­емый про­дол­жает работу. Это хорошо вид­но в выводе прог­раммы.

Вывод приложения jthread
Вы­вод при­ложе­ния jthread
 

Настраиваем VS Code

Су­щес­тву­ет мно­жес­тво редак­торов кода, и выбор — это воп­рос лич­ный и поч­ти религи­озный. Я исполь­зую VS Code и всем советую, но перед исполь­зовани­ем для кодин­га на C++ его нуж­но нем­ного нас­тро­ить.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    1 Комментарий
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии