HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-03-10
1 <p>В августе 2020 года был созван комитет по стандартам языка, чтобы завершить работу над предстоящим релизом новой версии С++ 20. И вот, у нас на руках появился свежий выпуск языка, некоторые особенности которого поддерживаются современными компиляторами.</p>
1 <p>В августе 2020 года был созван комитет по стандартам языка, чтобы завершить работу над предстоящим релизом новой версии С++ 20. И вот, у нас на руках появился свежий выпуск языка, некоторые особенности которого поддерживаются современными компиляторами.</p>
2 <p>Изначально язык С++ унаследован от языка С и назывался Си с классами, что позволяло работать в объектно-ориентированной парадигме. Это было в далёком 1980 году. С этого момента язык постоянно развивался и совершенствовался, особенно начиная с С++11 версии и выше. Стали появляться возможности обобщённого и функционального программирования, также новые версии языка дали возможность использовать многопоточное и асинхронное программирование. Новая версия языка соответствует тренду развития и также предоставляет новые возможности, некоторые из которых мы рассмотрим в этой статье.</p>
2 <p>Изначально язык С++ унаследован от языка С и назывался Си с классами, что позволяло работать в объектно-ориентированной парадигме. Это было в далёком 1980 году. С этого момента язык постоянно развивался и совершенствовался, особенно начиная с С++11 версии и выше. Стали появляться возможности обобщённого и функционального программирования, также новые версии языка дали возможность использовать многопоточное и асинхронное программирование. Новая версия языка соответствует тренду развития и также предоставляет новые возможности, некоторые из которых мы рассмотрим в этой статье.</p>
3 <h2>Оператор трехстороннего сравнения &lt;=&gt;</h2>
3 <h2>Оператор трехстороннего сравнения &lt;=&gt;</h2>
4 <p>Оператор трёхстороннего сравнения, часто называемый spaceship operator, определён для двух переменных A и B, где A &lt; B, A == B и A &gt; B. В итоге компилятор может сгенерировать код для шести вариантов сравнения: &lt;, &lt;=, ==, !=, &gt;, &gt;=. Далее приведён пример из блога компании Microsoft (https://devblogs.microsoft.com/cppblog/simplify-your-code-with-rocket-science-c20s-spaceship-operator/):</p>
4 <p>Оператор трёхстороннего сравнения, часто называемый spaceship operator, определён для двух переменных A и B, где A &lt; B, A == B и A &gt; B. В итоге компилятор может сгенерировать код для шести вариантов сравнения: &lt;, &lt;=, ==, !=, &gt;, &gt;=. Далее приведён пример из блога компании Microsoft (https://devblogs.microsoft.com/cppblog/simplify-your-code-with-rocket-science-c20s-spaceship-operator/):</p>
5 struct Basics { int i; char c; float f; double d; auto operator&lt;=&gt;(const Basics&amp;) const = default; }; struct Arrays { int ai[1]; char ac[2]; float af[3]; double ad[2][2]; auto operator&lt;=&gt;(const Arrays&amp;) const = default; }; struct Bases : Basics, Arrays { auto operator&lt;=&gt;(const Bases&amp;) const = default; }; int main() { constexpr Bases a = { { 0, 'c', 1.f, 1. }, { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } }; constexpr Bases b = { { 0, 'c', 1.f, 1. }, { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } }; static_assert(a == b); static_assert(!(a != b)); static_assert(!(a &lt; b)); static_assert(a &lt;= b); static_assert(!(a &gt; b)); static_assert(a &gt;= b); }<p>В результате мы избавляемся от огромного количества кода, ведь для каждого оператора сравнения нам нужно было бы переопределять свой метод. Также код становится более читаемым, с меньшим количеством ошибок и более безопасным.</p>
5 struct Basics { int i; char c; float f; double d; auto operator&lt;=&gt;(const Basics&amp;) const = default; }; struct Arrays { int ai[1]; char ac[2]; float af[3]; double ad[2][2]; auto operator&lt;=&gt;(const Arrays&amp;) const = default; }; struct Bases : Basics, Arrays { auto operator&lt;=&gt;(const Bases&amp;) const = default; }; int main() { constexpr Bases a = { { 0, 'c', 1.f, 1. }, { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } }; constexpr Bases b = { { 0, 'c', 1.f, 1. }, { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } }; static_assert(a == b); static_assert(!(a != b)); static_assert(!(a &lt; b)); static_assert(a &lt;= b); static_assert(!(a &gt; b)); static_assert(a &gt;= b); }<p>В результате мы избавляемся от огромного количества кода, ведь для каждого оператора сравнения нам нужно было бы переопределять свой метод. Также код становится более читаемым, с меньшим количеством ошибок и более безопасным.</p>
6 <h2>Избавляемся от макросов</h2>
6 <h2>Избавляемся от макросов</h2>
7 <p>В целом, разработчики стандарта стараются исключить препроцессор. Как следствие, в новой версии можно не пользоваться макросами<strong>FILE</strong>и<strong>LINE</strong>, а взамен использовать std::source_location. Например:</p>
7 <p>В целом, разработчики стандарта стараются исключить препроцессор. Как следствие, в новой версии можно не пользоваться макросами<strong>FILE</strong>и<strong>LINE</strong>, а взамен использовать std::source_location. Например:</p>
8 void log() { const std::experimental::source_location location = std::experimental::source_location::current(); std::cout &lt;&lt; "FName: " &lt;&lt; location.file_name() &lt;&lt; std::endl; std::cout &lt;&lt; "Line: " &lt;&lt; location.line() &lt;&lt; std::endl; std::cout &lt;&lt; "Func: " &lt;&lt; location.function_name() &lt;&lt; std::endl; }<p>Как мы видим, код становится более единообразным, в одном стиле, с расширяемым функционалом.</p>
8 void log() { const std::experimental::source_location location = std::experimental::source_location::current(); std::cout &lt;&lt; "FName: " &lt;&lt; location.file_name() &lt;&lt; std::endl; std::cout &lt;&lt; "Line: " &lt;&lt; location.line() &lt;&lt; std::endl; std::cout &lt;&lt; "Func: " &lt;&lt; location.function_name() &lt;&lt; std::endl; }<p>Как мы видим, код становится более единообразным, в одном стиле, с расширяемым функционалом.</p>
9 <h2>Явные константы</h2>
9 <h2>Явные константы</h2>
10 <p>В C++11 было введено ключевое слово<strong>constexpr</strong>, определяющее константное выражение, вычисляемое во время компиляции. Это позволило оптимизировать код и зачастую более корректно определять поведение функции еще во время компиляции.</p>
10 <p>В C++11 было введено ключевое слово<strong>constexpr</strong>, определяющее константное выражение, вычисляемое во время компиляции. Это позволило оптимизировать код и зачастую более корректно определять поведение функции еще во время компиляции.</p>
11 constexpr int foo(int factor) { return 123 * factor; } const int const_factor = 10; int non_const_factor = 20; const int first = foo(const_factor); const int second = foo(non_const_factor);<p>Здесь выражение<em>const int first = foo(const_factor);</em>будет вычислено в режиме компиляции, так как<em>const_factor</em>для компилятора является константой, а вот выражение<em>const int second = foo(non_const_factor);</em>будет вычислено в работающей программе, так как<em>non_const_factor</em>не является константой и к моменту вызова может быть любым.</p>
11 constexpr int foo(int factor) { return 123 * factor; } const int const_factor = 10; int non_const_factor = 20; const int first = foo(const_factor); const int second = foo(non_const_factor);<p>Здесь выражение<em>const int first = foo(const_factor);</em>будет вычислено в режиме компиляции, так как<em>const_factor</em>для компилятора является константой, а вот выражение<em>const int second = foo(non_const_factor);</em>будет вычислено в работающей программе, так как<em>non_const_factor</em>не является константой и к моменту вызова может быть любым.</p>
12 <p>В С++20 добавлен спецификатор<em>consteval</em>, который объявляет функцию или шаблон функции как константную функцию, то есть каждый потенциально оцениваемый вызов функции должен (прямо или косвенно) создавать выражение константы времени компиляции.</p>
12 <p>В С++20 добавлен спецификатор<em>consteval</em>, который объявляет функцию или шаблон функции как константную функцию, то есть каждый потенциально оцениваемый вызов функции должен (прямо или косвенно) создавать выражение константы времени компиляции.</p>
13 consteval int sqr ( int n ) { return n * n ; } constexpr int r = sqr ( 100 ) ; // Все в порядке int x = 100 ; int r2 = sqr ( х ) ; // Ошибка: вызов не создает константу consteval int sqrsqr ( int n ) { return sqr ( sqr ( n ) ) ; // На данном этапе непостоянное выражение, но все в порядке } constexpr int dblsqr ( int n ) { return 2 * sqr ( n ) ; // Ошибка: закрывающая функция не является consteval и sqr (n) не является константой }<p>С другой стороны проверки константного выражения находится новое ключевое слово<em>constinit</em>, которое говорит компилятору, что объект будет статически инициализирован с постоянным значением.</p>
13 consteval int sqr ( int n ) { return n * n ; } constexpr int r = sqr ( 100 ) ; // Все в порядке int x = 100 ; int r2 = sqr ( х ) ; // Ошибка: вызов не создает константу consteval int sqrsqr ( int n ) { return sqr ( sqr ( n ) ) ; // На данном этапе непостоянное выражение, но все в порядке } constexpr int dblsqr ( int n ) { return 2 * sqr ( n ) ; // Ошибка: закрывающая функция не является consteval и sqr (n) не является константой }<p>С другой стороны проверки константного выражения находится новое ключевое слово<em>constinit</em>, которое говорит компилятору, что объект будет статически инициализирован с постоянным значением.</p>
14 <p>В результате появляется возможность более точно определять состояние объекта, особенно при компиляции, что непосредственно будет влиять на читаемость кода, стабильность работы программ и быстродействие.</p>
14 <p>В результате появляется возможность более точно определять состояние объекта, особенно при компиляции, что непосредственно будет влиять на читаемость кода, стабильность работы программ и быстродействие.</p>
15 <h2>Строковые литералы как параметры шаблона</h2>
15 <h2>Строковые литералы как параметры шаблона</h2>
16 <p>Начиная с C ++ 20, вы можете использовать строку в качестве параметра шаблона, не являющегося типом. Идея состоит в том, чтобы использовать стандартную строку basic_fixed_string, которая имеет конструктор<strong>constexpr</strong>. Конструктор constexpr позволяет ему создать экземпляр фиксированной строки во время компиляции.</p>
16 <p>Начиная с C ++ 20, вы можете использовать строку в качестве параметра шаблона, не являющегося типом. Идея состоит в том, чтобы использовать стандартную строку basic_fixed_string, которая имеет конструктор<strong>constexpr</strong>. Конструктор constexpr позволяет ему создать экземпляр фиксированной строки во время компиляции.</p>
17 template&lt;std::basic_fixed_string T&gt; class Foo { static constexpr char const* Name = T; public: void hello() const; }; int main() { Foo&lt;"Hello!"&gt; foo; foo.hello(); }<p>Вроде бы мелочь, а приятно - не нужно производить обходных маневров и использовать лишнюю память.</p>
17 template&lt;std::basic_fixed_string T&gt; class Foo { static constexpr char const* Name = T; public: void hello() const; }; int main() { Foo&lt;"Hello!"&gt; foo; foo.hello(); }<p>Вроде бы мелочь, а приятно - не нужно производить обходных маневров и использовать лишнюю память.</p>
18 <h2>malloc стал безопасен</h2>
18 <h2>malloc стал безопасен</h2>
19 <p>В предыдущих версиях использование низкоуровневых функций, унаследованных из языка Си, не рекомендовалось. Проблема в том, что Си оперирует байтами, а в С++ происходит работа с объектами со своим временем жизни и областью видимости. До С++ 20 время жизни объекта начиналось после вызова оператора<em>new</em>. В новой версии все изменилось - принято считать, что набор низкоуровневых функций - memcpy, memmove, malloc, aligned_alloc, calloc, realloc, bit_cast, начинает время жизни объекта. Т. е. следующий код будет валиден:</p>
19 <p>В предыдущих версиях использование низкоуровневых функций, унаследованных из языка Си, не рекомендовалось. Проблема в том, что Си оперирует байтами, а в С++ происходит работа с объектами со своим временем жизни и областью видимости. До С++ 20 время жизни объекта начиналось после вызова оператора<em>new</em>. В новой версии все изменилось - принято считать, что набор низкоуровневых функций - memcpy, memmove, malloc, aligned_alloc, calloc, realloc, bit_cast, начинает время жизни объекта. Т. е. следующий код будет валиден:</p>
20 struct X { int a, b; }; X *make_x() { X *p = (X*)malloc(sizeof(struct X)); p-&gt;a = 1; p-&gt;b = 2; return p; }<p>Т. е. у нас появляется обратная совместимость с языком Си, но относительно С++ в новой трактовке.</p>
20 struct X { int a, b; }; X *make_x() { X *p = (X*)malloc(sizeof(struct X)); p-&gt;a = 1; p-&gt;b = 2; return p; }<p>Т. е. у нас появляется обратная совместимость с языком Си, но относительно С++ в новой трактовке.</p>
21 <h2>Различные улучшения лямбда</h2>
21 <h2>Различные улучшения лямбда</h2>
22 <p>Относительно недавно в языке С++ появилась возможность использовать некоторые элементы из функционального программирования. Один из них - это<em>Lambda</em>-функции. Новый стандарт создает много улучшений для<em>Lambda</em>. Например:</p>
22 <p>Относительно недавно в языке С++ появилась возможность использовать некоторые элементы из функционального программирования. Один из них - это<em>Lambda</em>-функции. Новый стандарт создает много улучшений для<em>Lambda</em>. Например:</p>
23 <p><strong>1.Разрешить [=,this] как лямбда-захват и исключить неявный захват через [=].</strong></p>
23 <p><strong>1.Разрешить [=,this] как лямбда-захват и исключить неявный захват через [=].</strong></p>
24 <p>До С++20, чтобы захватить все внешние переменные по значению для<em>Lambda</em>, использовалось [=].</p>
24 <p>До С++20, чтобы захватить все внешние переменные по значению для<em>Lambda</em>, использовалось [=].</p>
25 struct Lambda { auto foo () { return [ = ] {std :: cout &lt;&lt; s &lt;&lt; std :: endl; }; } std :: string s; };<p>В С++20 это нежелательно - компилятор будет выдавать предупреждения, поэтому предпочтительно использовать другой синтаксис - [=,this].</p>
25 struct Lambda { auto foo () { return [ = ] {std :: cout &lt;&lt; s &lt;&lt; std :: endl; }; } std :: string s; };<p>В С++20 это нежелательно - компилятор будет выдавать предупреждения, поэтому предпочтительно использовать другой синтаксис - [=,this].</p>
26 struct LambdaCpp20 { auto foo () { return [ = , this ] {std :: cout &lt;&lt; s &lt;&lt; std :: endl; }; } std :: string s; };<p><strong>2.Шаблонные лямбды</strong></p>
26 struct LambdaCpp20 { auto foo () { return [ = , this ] {std :: cout &lt;&lt; s &lt;&lt; std :: endl; }; } std :: string s; };<p><strong>2.Шаблонные лямбды</strong></p>
27 <p>На первый взгляд, это нововведение выглядит необычно, поскольку возникает вопрос: "Зачем нам нужны лямбда-выражения шаблонов"? Ведь когда вы пишете общую лямбду [] (auto x) {return x; }, компилятор автоматически генерирует класс с шаблонным оператором вызова, например:</p>
27 <p>На первый взгляд, это нововведение выглядит необычно, поскольку возникает вопрос: "Зачем нам нужны лямбда-выражения шаблонов"? Ведь когда вы пишете общую лямбду [] (auto x) {return x; }, компилятор автоматически генерирует класс с шаблонным оператором вызова, например:</p>
28 template &lt; typename T &gt; Оператор T (T x) const { return x; }<p>Но иногда необходимо определить лямбду, которая будет работать только для одного типа, например, для std::vector, тогда нам на помощь приходят лямбда-шаблоны, где вместо параметра типа можно также использовать концепции, например:</p>
28 template &lt; typename T &gt; Оператор T (T x) const { return x; }<p>Но иногда необходимо определить лямбду, которая будет работать только для одного типа, например, для std::vector, тогда нам на помощь приходят лямбда-шаблоны, где вместо параметра типа можно также использовать концепции, например:</p>
29 auto foo = [] &lt; typename T &gt; (std::vector &lt;T&gt; const &amp; vec) { // делаем специфичные для вектора вещи };<h2>Новые атрибуты [[likely]] и[[unlikely]]</h2>
29 auto foo = [] &lt; typename T &gt; (std::vector &lt;T&gt; const &amp; vec) { // делаем специфичные для вектора вещи };<h2>Новые атрибуты [[likely]] и[[unlikely]]</h2>
30 <p>В C++20 мы получаем новые атрибуты [[likely]] и [[unlikely]], которые позволяют подсказывать оптимизатору, является ли путь выполнения более или менее вероятным.</p>
30 <p>В C++20 мы получаем новые атрибуты [[likely]] и [[unlikely]], которые позволяют подсказывать оптимизатору, является ли путь выполнения более или менее вероятным.</p>
31 for(size_t i=0; i &lt; v.size(); ++i){ if (v[i] &lt; 0) [[likely]] sum -= sqrt(-v[i]); else sum += sqrt(v[i]); }<h2>Заключение</h2>
31 for(size_t i=0; i &lt; v.size(); ++i){ if (v[i] &lt; 0) [[likely]] sum -= sqrt(-v[i]); else sum += sqrt(v[i]); }<h2>Заключение</h2>
32 <p>Это только небольшая часть новых возможностей стандарта. Безусловно, каждое из перечисленных нововведений в язык можно детально обсуждать в целой статье, что делается многими авторами. Следует упомянуть, что не вошло в данный обзор: • coroutines - ещё одна важная функция; • новая библиотека синхронизации; • кооперативное прерывание потоков; • using enum для уменьшения шума от разделения пространств имен; • частичный отказ от volatile; • концепции; • использование модулей вместо #include.</p>
32 <p>Это только небольшая часть новых возможностей стандарта. Безусловно, каждое из перечисленных нововведений в язык можно детально обсуждать в целой статье, что делается многими авторами. Следует упомянуть, что не вошло в данный обзор: • coroutines - ещё одна важная функция; • новая библиотека синхронизации; • кооперативное прерывание потоков; • using enum для уменьшения шума от разделения пространств имен; • частичный отказ от volatile; • концепции; • использование модулей вместо #include.</p>
33 <p>Как мы видим, С++ очень динамично развивается. Язык становится более безопасный и стабильный. Добавляется много элементов языка для улучшения оптимизации и читаемости кода. Появляются новые функциональные возможности. В целом, можно сказать, что на современном С++ можно писать красивый и лаконичный код, которым можно восхищаться и гордиться.</p>
33 <p>Как мы видим, С++ очень динамично развивается. Язык становится более безопасный и стабильный. Добавляется много элементов языка для улучшения оптимизации и читаемости кода. Появляются новые функциональные возможности. В целом, можно сказать, что на современном С++ можно писать красивый и лаконичный код, которым можно восхищаться и гордиться.</p>
34  
34