[Перевод] Интригующие возможности С++ 20 для разработчиков встраиваемых систем
Си по-прежнему остаётся любимым языком программирования среди разработчиков встраиваемых систем, однако и среди них есть достаточное число тех, кто использует в своей практике С++.
Используя соответствующие возможности С++ можно написать код, который не будет уступать по своей эффективности коду аналогичного приложения, написанного на Си, а в ряде случаев он будет даже более эффективным, поскольку рядовому программисту может быть достаточно утомительно реализовывать некоторый функционал на Си, который гораздо проще реализуется с помощью дополнительных возможностей С++.
С++20 — это седьмая итерация С++, которой предшествовали, например, С++17, С++14 и С++11. Каждая итерация добавляла новые функциональные возможности и при этом влияла на пару функций, добавленных ранее. Например, ключевое слово auto С++14.
Прим. перев.:
В С++14 были введены новые правила для ключевого слова auto. Ранее выражения
auto a{1, 2, 3}, b{1};
были разрешены и обе переменные имели типinitializer_list
. В С++14auto a{1, 2, 3};
приводит к ошибке компиляции, аauto b{1};
успешно компилируется, но тип переменной будетint
, а неinitializer_list
. Эти правила не распространяются на выраженияauto a = {1, 2, 3}, b = {1};
, в которых переменные по-прежнему имеют типinitializer_list
.
В С++11 были внесены значительные изменения, и она является наиболее распространённой версией стандарта, которая применяется во встраиваемых системах, так как разработчики встраиваемых систем не всегда используют самый современный инструментарий. Проверенные и надежные решения имеют решающее значение при разработке платформы, которая может работать десятилетиями.
Так уж получилось, что в С++20 было добавлено довольно много новых функциональных возможностей. Новые итераторы и поддержка форматирования строк будут полезны с новой библиотекой синхронизации. У всех на слуху оператор трехстороннего сравнения, он же оператор «космический корабль». Как и большинство функциональных возможностей, описание этого оператора выходит за рамки данной статьи, но если кратко, то сравнение типа x < 20
, сначала будет преобразовано в x.operator<=>(20) < 0
. Таким образом, поддержка сравнения, для обработки операторов типа <, <=, >= и >, может быть реализована с помощью одной или двух операторных функций, а не дюжины. Выражение типа x == y
преобразуется в operator<=>(x, y) == 0
.
Прим. перев.:
Более подробную информацию об операторе «космический корабль» смотрите в статье @Viistomin «Новый оператор spaceship (космический корабль) в C++20»
Но прейдём к более интересным вещам и рассмотрим функциональные возможности С++20 которые будут интересны разработчикам встраиваемых систем, а именно:
Константы этапа компиляции
Разработчикам встраиваемых систем нравится возможность делать что-то на этапе компиляции программы, а не на этапе её выполнения. С++11 добавил ключевое слово constexpr позволяющее определять функции, которые вычисляются на этапе компиляции. С++20 расширил эту функциональность, теперь она распространяется и на виртуальные функции. Более того, её можно применять с конструкциями try/catch. Конечно есть ряд исключений.
Новое ключевое слово consteval тесно связано с constexpr что, по сути, делает его альтернативой макросам, которые наряду с константами, определенными через директиву #define, являются проклятием Си и С++.
Сопрограммы
Поддержка сопрограмм часто обеспечивается средствами компилятора и некоторыми операционными системами. Сопрограммы обеспечивают определенную форму многозадачности и могут упростить реализацию конечных автоматов и других приложений, таких как циклический планировщик. По сути такая задача сама передает управление (прим. перев.: кооперативная многозадачность) и в последствии возобновляет свою работу из этой точки.
Прим. перев.:
Более подробную информацию о сопрограммах и о том для чего они нужны, смотрите в статье @PkXwmpgN «C++20. Coroutines» и в ответе на вопрос, заданный на stackoverflow.
С++20 поддерживает функционал сопрограмм с помощью coroutine_traits и coroutine_handle. Сопрограммы являются стеконезависимыми и сохраняют свое состояние в «куче». Могут передавать управление и при необходимости предоставлять результат своей работы себе же, когда выполнение сопрограммы возобновляется.
Концепты и ограничения
Концепты и ограничения были экспериментальной функциональной возможностью С++17, а теперь являются стандартной. Поэтому можно предположить, что эксперимент прошел успешно. Если вы надеялись на контракты Ada и SPARK, то это не тот случай, но концепты и ограничения C++20 являются ценными дополнениями.
Плюсом ограничений является то, что они позволяют обнаруживать ошибки на этапе компиляции программы. Ограничения могут накладываться на шаблоны классов, шаблоны функций и обычные функции. Ограничение — это предикат (прим. перев.: утверждение), который проверяется на этапе компиляции программы. Именованный набор таких определений называется концептом. Пример кода, взятый с cppreference.com, показывает синтаксис и семантику этой функциональной возможности:
#include
#include
#include
using namespace std::literals;
// Объявляем концепт "Hashable", которому удовлетворяет
// любой тип 'T' такой, что для значения 'a' типа 'T',
// компилируется выражение std::hash{}(a) и его результат преобразуется в std::size_t
template
concept Hashable = requires(T a) {
{ std::hash{}(a) } -> std::convertible_to;
};
struct meow {};
template
void f(T); // Ограниченный шаблон функции С++20
// Другие способы применения того же самого ограничение:
// template
// requires Hashable
// void f(T);
//
// template
// void f(T) requires Hashable;
int main() {
f("abc"s); // OK, std::string удовлетворяет Hashable
f(meow{}); // Ошибка: meow не удовлетворяет Hashable
}
Концепты и ограничения являются наиболее значимой функциональной возможностью С++20, которая ориентирована на создание безопасных и надежных приложений, для таких областей, как транспорт, авиация и медицина.
Модули
Повсеместный #include заменяется модулями. Ключевые слова import и export находятся там, где когда-то находился #include.
Директива #include упростила написание компиляторов. Когда компилятор её встречает он просто выполняет чтение файла, указанного в ней. По сути получается, что включаемый файл как бы является частью оригинального файла. Такой подход зависит от порядка, в котором объединяются файлы. В целом это работает, однако такое решение плохо масштабируется при работе с большими и сложными системами, особенно с учетом всех взаимосвязей, которые могут принести шаблоны и классы С++.
Системы сборки на основе модулей широко распространены, и такие языки, как Java и Ada уже используют подобную систему. По сути, модульная система позволяет загружать модули только один раз на этапе компиляции, даже если они используются несколькими модулями. Порядок импортирования модулей не важен. Более того, модули могут явно предоставлять доступ к своим внутренним компонентам, что невозможно при применении директивы #include.
Существующие стандартные библиотеки теперь будут поддерживать включение в виде модулей, но для обеспечения обратной совместимости поддержка директивы #include остается. Модули хорошо работают с существующей поддержкой пространства имен.
Я по-прежнему склонен программировать на Ada и SPARK, но новые изменения в C++20 делают его лучшей платформой C++ для разработки безопасного и надежного программного обеспечения. На самом деле мне нужно поработать с новыми функциями C++20, чтобы увидеть, как они влияют на мой стиль программирования. Я старался избегать сопрограмм, поскольку они были нестандартными. Хотя концепты и ограничения будут немного сложнее, они будут более полезны в долгосрочной перспективе.
Положительным моментом является то, что компиляторы с открытым исходным кодом поддерживают C++20, а также то, что в сети интернет появляется всё больше коммерческих версий компиляторов, поддерживающих С++20.