Как пользоваться атрибутами из C++17

Sergey Shambir
3 min readJun 27, 2017

Вы читаете перевод статьи Using C++17 Attributes Today.

C++11 добавил в язык универсальные атрибуты, позволяющие сообщить компилятору дополнительную инфомацию о каком-либо символе или выражении в коде. В прошлом ведущие компиляторы предлагали для этого собственные механизмы, такие как __attribute__(deprecated) в GNU GCC и LLVM/Clang и __declspec(deprecated) в Visual C++. Универсальные атрибуты, они же ряды спецификации атрибутов, позволяют выражать то же самое независимым от компилятора способом, [[deprecated]].

Каждый атрибут попадает в стандарт отдельно. Атрибут [[deprecated]] (“устарело”) был стандартизирован в C++14, хотя в виде нестандартных расширений он был доступен в GCC, CLang и Visual C++ намного раньше. Если вы прячете атрибут за макросом, то для написания переносимого кода следует использовать простую форму атрибута, не принимающую аргументом строку с описанием причины, почему API устарел.

Новые атрибуты в C++17

В C++17 в стандарт попали три новых атрибута, которые позволяют управлять появлением различных предупреждений компилятора.

  • nodiscard указывает, что возвращаемое функцией значение нельзя игнорировать и нужно сохранить в какую-либо переменную
  • fallthrough ставится перед case-веткой в switch и указывает, что в данном месте намеренно пропущен break
  • maybe_unused заставляет компилятор погасить предупреждения о переменной, которая не используется в некоторых режимах компиляции (например, код возврата функции всего лишь проверяется в assert)

К сожалению, стандарт C++17 на момент написания ещё только начали внедрять. Пройдут годы, прежде чем большинство программистов сможет свободно использовать атрибуты в production.

Обнаружение атрибутов

Одна из технических рекомендаций стандарта предлагает встроенный макрос для проверки доступности атрибута: __has_cpp_attribute. Поддержка появилась в Clang с версии 3.6 и в GCC с версии 5. Если же макрос недоступен, можно добавить заглушку:

Применение атрибута nodiscard

В проектах с высокими требованиями к производительности могут практиковать отказ от выброса исключений (по крайней мере в некоторых компонентах). В таких случаях об ошибке выполнения операции сообщает код возврата, возвращённый из функции. Однако, очень легко забыть проверить этот код, и на помощь приходит nodiscard.

Указать атрибут nodiscard можно в объявлении функции:

Однако, если вы используете собственный класс для кодов ошибок, то вы можете указать атрибут только один раз в определении класса.

Это особенно полезно при включённой опции “warnings as errors” или при регулярной проверке отсутствия предупреждений на билдсервере. Вы ещё при комплиляции проверяете, что у вас имеется обработка ошибок.

Атрибут nodiscard был стандартизирован в C++17, но ещё до этого существовал под именем gnu::warn_unused_result в компиляторах GCC с давних времён и Clang с версии 3.6. Используя механизм обнаружения атрибутов, вы можете объявить макрос, добавляющий атрибут nodiscard:

Применение атрибута fallthrough

Доставляет множество неудобств необходимость добавлять break после каждого case в конструкции switch. Несмотря на то, что язык не поддерживает неявный break, компилятор всё же может помочь. Атрибут fallthrough был впервые введён в Clang под именем clang::fallthroughв версии 3.6, а в C++17 он вошёл в более короткой форме. Мы также можем объявить макрос, добавляющий атрибут:

Чтобы воспользоваться преимуществами атрибута, в GCC и Clang следует включит предупреждение -Wimplicit-fallthrough. После включения этой опции каждый case, не имеющий атрибута fallthrough, будет порождать предупреждение.

Заметим, что атрибут fallthrough должен быть внутри пустой инструкции, располагающейся непосредственно перед следующим case

Применение атрибута maybe_unused

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

Цель атрибута maybe_unused — погасить предупреждения от отладочных переменных. Такое решение замещает старые, менее очевидные для новичков и более многословные решения.

Атрибуты из C++11 и C++14

В предыдущих версиях стандарта уже было внесено несколько атрибутов, но они не получили широкого распространения, видимо, из-за отсутствия поддержки в определённых компиляторах и малого числа атрибутов. Но теперь, с выходом C++17, эти атрибуты можно и нужно использовать чаще.

  • атрибут [[noreturn]] применим только к объявлению функции и обозначает, что она не возвращает управление привычным путём; данным атрибутом помечен рад стандартных функций, включая std::abort, std::exit, std::quick_exit, std::longjmp, std::terminate, std::rethrow_exception.
  • атрибуты [[deprecated]] и [[deprecated("reason")]] полезны для авторов библиотек: они порождают предупреждение об устаревшем API при попытке использования помеченных данным атрибутом функций, констант, переменных, классов, синонимов типов и перечислений.

Заключение

Поддержка атрибутов maybe_unused, nodiscard, fallthrough из стандарта C++17 должна появиться в компиляторах уже в ближайшее время. Например, Clang 3.9 реализовал эти атрибуты и даже включает их при компиляции в режиме C++11 и выше. Если же вы не можете пользоваться новейшими компиляторами, то, по крайней мере, вы можете абстрагировать работу с атрибутами с помощью макросов.

--

--