Что такое атрибуты в C++?

1,00
р.
Начиная со стандарта c++11 в языке появились так называемые "атрибуты", что это такое и зачем они нужны?

Ответ
Атрибуты позволяют задавать дополнительную информацию для различных конструкций языка, таких как типы, переменные, имена, блоки и единицы трансляции. Данная информация в частности может быть использована компилятором для генерации более эффективного кода и предоставления (или наоборот, подавления) предупреждающих сообщений пользователю на уровне конкретных участков кода, а не целой программы или компилируемого файла, как это обеспечивается ключами компиляции.
Атрибуты появились в c++11 и впоследствии были несколько расширены. На данный момент существуют следующие стандартные атрибуты:
assume carries_dependency deprecated fallthrough likely и unlikely maybe_unused nodiscard noreturn no_unique_address
Набор атрибутов может быть расширен каждой конкретной реализацией компилятора, в таком случае его поведение будет описываться отдельно. Неизвестный атрибут будет проигнорирован, но может быть выведено предупреждающее сообщение.
Атрибут всегда обрамляется двойными квадратными скобками:
[[атрибут]]

Пойдём по порядку:
[[assume]]
Атрибут (доступен начиная с c++23) применяется к пустой операции . Это интерпретируется как предположение. Аргумент атрибута обязан присутствовать и иметь форму (условное_выражение). Тип выражения интерпретируется как bool, а само выражение не является вычисляемым, т.е. не меняет состояние программы. Если результат выражения интерпретируется как true, то предположение не имеет эффекта, в противном случае - поведение не определено. Предположение позволяет компилятору анализировать форму выражения и использовать этот результат для целей оптимизации программы, но это не является обязательным и может быть проигнорировано.
int divide_by_32(int x) { [[assume(x >= 0)]] return x/32 // Машинные инструкции, сгенерированные для деления // могут пропустить обработку отрицательных чисел } int f(int y) { [[assume(++y == 43)]] // `y` не будет увеличен, return y // выражение может быть заменено на return 42 }
[[carries_dependency]]
Данный атрибут не изменяет смысл программы, но может приводить к генерации более эффективного кода. Атрибут может применяться как к целой функции, так и к её параметрам. Может быть полезен на системах со слабо упорядоченной архитектурой при передаче значения между вычислительными потоками. Самый сложный для понимания атрибут :) Поэтому за дополнительной информацией - на enSO.
[[deprecated]]
Позволяет отметить сущность устаревшей или небезопасной, но тем не менее пока ещё разрешённой к использованию. Может применяться к объявлению класса, typedef-имени, переменной, нестатическому члену данных, функции, пространству имён, перечислению, элементу перечисления или специализации шаблона. Атрибут может быть снабжен аргументом, заданным строковым литералом. Например:
[[deprecated("используйте функцию g()")]] void f()
Текстовое сообщение будет использовано как подсказка при выводе соответствующего предупреждения.
[[fallthrough]]
Данный атрибут применяется к пустой операции, т.е. . Может быть использован только внутри switch для уведомления компилятора о задуманном программистом "проваливании" цепочки действий из одной ветки case в другую. Атрибут позволяет подавлять предупреждение компилятора, которое он может выдать, если между метками case не будет обнаружен оператор break. Часто отсутствие break может быть банальной ошибкой, возникшей по невнимательности. Пример:
switch (i) { case 1: ... [[fallthrough]] case 2: ... }
[[likely]] и [[unlikely]]
Атрибуты могут быть добавлены к меткам case или операторам (statement) для подсказки компилятору, что тот или иной участок кода ожидается наиболее вероятным (likely) или, наоборот, менее вероятным (unlikely) при выполнении программы. Пример:
void g(int) int f(int n) { if (n > 5) [[unlikely]] { // n > 5 маловероятная ветка g(0) return n * 2 + 1 }
switch (n) { case 1: g(1) [[fallthrough]]
[[likely]] case 2: // n == 2 более вероятное значение g(2) // нежели любое другое n break } return 3 }
[[maybe_unused]]
Используется для уведомления компилятора о том, что сущность может быть не использована в программе и следует подавлять соответствующее предупреждение. Может применяться к объявлению класса, typedef-имени, переменной, нестатическому члену данных, функции, пространству имён, перечислению или элементу перечисления. Атрибут может быть полезен при наличии отладочного кода, который не будет включён в бинарник при сборке в release режиме. Пример:
[[maybe_unused]] void f([[maybe_unused]] bool thing1, [[maybe_unused]] bool thing2) { [[maybe_unused]] bool b = thing1 && thing2 assert(b) }
Ранее приходилось использовать приведение к void для подавления возможных предупреждений.
[[nodiscard]]
Указывает на обязательность использования результата при возврате из функции. Может быть применим как к типу (при объявлении класса или перечисления), так и непосредственно к возвращаемому типу функции. Пример:
[[nodiscard]] int f() { return 42 } ... f() // сообщение о том, что результат функции не использован
Явное приведение результата к void подавляет действие атрибута:
static_cast(f()) // нет предупреждения о не использованном результате
Альтернативно можно использовать присваивание std::ignore:
#include std::ignore = f() // нет предупреждения о не использованном результате
Начиная с C++20 можно использовать дополнительный строковый литерал для пояснения причины наличия атрибута по аналогии с атрибутом [[deprecated]].
[[noreturn]]
Говорит о том, что функция не возвращает управление. Может быть применим к объявлению функции. Актуально для функций, которые заканчивают свою работу выкидыванием исключения, выполняют вечный цикл или прерывают выполнение всей программы. Пример:
[[noreturn]] void f() { throw "error" }
Если функция, помеченная атрибутом [[noreturn]], возвращает управление на одной из веток выполнения, то это приводит к неопределённому поведению.
[[no_unique_address]]
Указывает на то, что нестатический член данных класса является потенциально-перекрываемым (potentially-overlapping) подобъектом (не может применяться к битовым полям). Это значит что член может иметь общий адрес с другим нестатическим членом данных этого или базового класса, а заполнители, которые обычно вставляются в конец объекта, могут быть использованы для хранения других членов. Пример:
template class hash_map { [[no_unique_address]] Hash hasher [[no_unique_address]] Pred pred [[no_unique_address]] Allocator alloc Bucket *buckets // ... public: // ... }
Здесь hasher, pred и alloc могут иметь тот же адрес, что и buckets, если соответствующие им типы окажутся пустыми.