Начиная со стандарта c++11 в языке появились так называемые "атрибуты", что это такое и зачем они нужны?
1 Answers
Атрибуты позволяют задавать дополнительную информацию для различных конструкций языка, таких как типы, переменные, имена, блоки и единицы трансляции. Данная информация в частности может быть использована компилятором для генерации более эффективного кода и предоставления (или наоборот, подавления) предупреждающих сообщений пользователю на уровне конкретных участков кода, а не целой программы или компилируемого файла, как это обеспечивается ключами компиляции.
Атрибуты появились в c++11 и впоследствии были несколько расширены. На данный момент существуют следующие стандартные атрибуты:
assumecarries_dependencydeprecatedfallthroughlikelyиunlikelymaybe_unusednodiscardnoreturnno_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<void>(f()); // нет предупреждения о не использованном результате
Альтернативно можно использовать присваивание std::ignore:
#include <tuple>
std::ignore = f(); // нет предупреждения о не использованном результате
Начиная с C++20 можно использовать дополнительный строковый литерал для пояснения причины наличия атрибута по аналогии с атрибутом [[deprecated]].
[[noreturn]]
Говорит о том, что функция не возвращает управление. Может быть применим к объявлению функции. Актуально для функций, которые заканчивают свою работу выкидыванием исключения, выполняют вечный цикл или прерывают выполнение всей программы. Пример:
[[noreturn]]
void f() {
throw "error";
}
Если функция, помеченная атрибутом [[noreturn]], возвращает управление на одной из веток выполнения, то это приводит к неопределённому поведению.
[[no_unique_address]]
Указывает на то, что нестатический член данных класса является потенциально-перекрываемым (potentially-overlapping) подобъектом (не может применяться к битовым полям). Это значит что член может иметь общий адрес с другим нестатическим членом данных этого или базового класса, а заполнители, которые обычно вставляются в конец объекта, могут быть использованы для хранения других членов. Пример:
template<typename Key, typename Value,
typename Hash, typename Pred, typename Allocator>
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, если соответствующие им типы окажутся пустыми.
- 28,987
- 13
- 60
- 119
[[no_unique_address]]можно довольно легко добиться экономии памяти? Насколько это эффективно в сравнении с использованием объединений? – AvidCoder Mar 06 '21 at 19:56[[no_unique_address]]это для пустых типов, в которых нет членов-данных. А объединенияunionдля разделения памяти между объектами, которые хранят данные. – αλεχολυτ Oct 27 '21 at 10:20