атрибуты в C ++

Опубликовано: 5 Января, 2022

Атрибуты - одна из ключевых особенностей современного C ++, которая позволяет программисту указывать дополнительную информацию для компилятора, чтобы обеспечить соблюдение ограничений (условий), оптимизировать определенные фрагменты кода или выполнить генерацию определенного кода . Проще говоря, атрибут действует как аннотация или примечание для компилятора, которое предоставляет дополнительную информацию о коде для целей оптимизации и выполнения определенных условий для него. Представленные в C ++ 11, они остались одной из лучших функций C ++ и постоянно развиваются с каждой новой версией C ++.

Синтаксис:

// C++11
[[attribute-list]]

// C++17
[[using attribute-namespace:attribute-list]]

// Upcoming C++20
[[contract-attribute-token contract-level identifier : expression]]

Except for some specific ones, most of the attributes
can be applied with variables, functions, classes,
structures etc.

Назначение атрибутов в C ++

  • Чтобы наложить ограничения на код:

    Здесь ограничение относится к условию, которому должны соответствовать аргументы конкретной функции для ее выполнения (предварительное условие). В предыдущих версиях C ++ код для задания ограничений был написан таким образом.

    int f( int i)
    {
    if (i > 0)
    return i;
    else
    return -1;
    // Code
    }

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

    int f( int i)[[expects:i > 0]]
    {
    // Code
    }
  • Чтобы предоставить компилятору дополнительную информацию в целях оптимизации:

    Компиляторы очень хороши в оптимизации, но по сравнению с людьми они все еще отстают в некоторых местах и предлагают обобщенный код, который не очень эффективен. В основном это происходит из-за отсутствия дополнительной информации о «проблеме», которая есть у людей. Чтобы до некоторой степени уменьшить эту проблему, стандарт C ++ ввел некоторые новые атрибуты, которые позволяют указать компилятору немного больше, а не сам оператор кода. Когда-то такой пример вполне вероятен.

    int f( int i)
    {
    switch (i) {
    case 1:
    [[fallthrough]];
    [[likely]] case 2 : return 1;
    }
    return -1;
    }

    Когда оператору предшествует вероятный, компилятор выполняет специальные оптимизации по отношению к этому оператору, что улучшает общую производительность кода. Некоторые примеры таких атрибутов: [supports_dependency], [вероятно], [маловероятно]

  • Подавление определенных предупреждений и ошибок, которые программист намеревался иметь в своем коде:

    Это случается редко, но иногда программист намеренно пытается написать ошибочный код, который обнаруживается компилятором и отображается как ошибка или предупреждение. Одним из таких примеров является неиспользованная переменная, которая осталась в этом состоянии по определенной причине, или оператор switch, в котором операторы break не помещаются после некоторых случаев, чтобы вызвать условия падения. Чтобы избежать ошибок и предупреждений в таких условиях, C ++ предоставляет такие атрибуты, как [might_unused] и [fallthrough], которые не позволяют компилятору генерировать предупреждения или ошибки.

    #include <iostream>
    #include <string>
    int main()
    {
    // Set debug mode in compiler or 'R'
    [[maybe_unused]] char mg_brk = 'D' ;
    // Compiler does not emit any warnings
    // or error on this unused variable
    }

Список стандартных атрибутов в C ++

    C ++ 11

  1. noreturn: указывает, что функция не возвращает значение



    Использование:

    [[noreturn]] void f ();
    

    При взгляде на приведенный выше код возникает вопрос: какой смысл в noreturn, если возвращаемый тип фактически недействителен ? Если функция имеет тип void, тогда она фактически возвращается к вызывающей стороне без значения, но если случай таков, что функция никогда не возвращается обратно к вызывающей стороне (например, бесконечный цикл), то добавление атрибута noreturn дает подсказки компилятору. для оптимизации кода или генерации более точных предупреждений.

    Пример:

    #include <iostream>
    #include <string>
    [[ noreturn ]] void f()
    {
    // Some code that does not return
    // back the control to the caller
    // In this case the function returns
    // back to the caller without a value
    // This is the reason why the
    // warning "noreturn' function does return' arises
    }
    void g()
    {
    std::cout << "Code is intented to reach here" ;
    }
    int main()
    {
    f();
    g();
    }

    Предупреждение:

    main.cpp: В функции void f ():
    main.cpp: 8: 1: предупреждение: функция 'noreturn' не возвращает
     }
     ^
    

    C ++ 14

  2. deprecated: указывает, что имя или объект, объявленные с этим атрибутом, устарели и не должны использоваться по какой-либо конкретной причине. Этот атрибут может применяться к пространствам имен, функциям, структурам классов или переменным.

    Использование:

    [[устарело ("Причина прекращения поддержки")]]
    
    // Для Class / Struct / Union
    struct [[не рекомендуется]] S;
    
    // Для функций
    [[не рекомендуется]] void f ();
    
    // Для пространств имен
    пространство имен [[не рекомендуется]] нс {}
    
    // Для переменных (включая статические элементы данных)
    [[не рекомендуется]] int x;
    
    

    Пример:

    #include <iostream>
    #include <string>
    [[ deprecated ( "Susceptible to buffer overflow" )]] void gets ( char * str)
    {
    // Code for gets dummy
    // (Although original function has
    // char* as return type)
    }
    void gets_n(std::string& str)
    {
    // Dummy code
    char st[100];
    std::cout << "Successfully Executed" ;
    std::cin.getline(st, 100);
    str = std::string(st);
    // Code for new gets
    }
    int main()
    {
    char a[100];
    gets (a);
    // std::string str;
    // gets_n(str);
    }

    Предупреждение:

    main.cpp: В функции int main ():
    main.cpp: 26: 9: предупреждение: void gets (char *) устарело:
     Чувствительность к переполнению буфера [-Wdeprecated-declrations]
       получает (а);
             ^
    

    C ++ 17

  3. nodiscard: объекты, объявленные с помощью nodiscard, не должны игнорировать возвращаемые значения вызывающей стороной. Проще говоря, если функция возвращает значение и помечена как nodiscard, то возвращаемое значение должно использоваться вызывающей стороной, а не отбрасываться.

    Использование :

    // Функции
    [[nodiscard]] void f ();
    
    // Объявление класса / структуры
    struct [[nodiscard]] my_struct {};
    

    Основное различие между nodiscard с функциями и nodiscard с объявлением структуры / класса заключается в том, что в случае функции nodiscard применяется только к той конкретной функции, которая объявлена без отбрасывания, тогда как в случае объявления класса / структуры nodiscard применяется к каждой отдельной функции, которая возвращает nodiscard помечает объект по значению.

    Пример:

    #include <iostream>
    #include <string>
    // Return value must be utilized by the caller
    [[nodiscard]] int f()
    {
    return 0;
    }
    class [[nodiscard]] my_class{};
    // Automatically becomes nodiscard marked
    my_class fun()
    {
    return my_class();
    }
    int main()
    {
    int x{ 1 };
    // No error as value is utilised
    // x= f();
    // Error : Value is not utilised
    f();
    // Value not utilised error
    // fun() ;
    return x;
    }

    Предупреждение:

    prog.cpp: 5: 21: предупреждение: директива атрибута 'nodiscard' игнорируется [-Wattributes]
     [[nodiscard]] int f ()
                         ^
    prog.cpp: 10: 20: предупреждение: директива атрибута 'nodiscard' игнорируется [-Wattributes]
     класс [[nodiscard]] my_class {};
                        ^
    
  4. may_unused : используется для подавления предупреждений о любых неиспользуемых объектах (например, неиспользованная переменная или неиспользуемый аргумент функции).

    Использование:

    // Переменные
    [[might_used]] bool log_var = true;
    
    // Функции
    [[возможно_unused]] недействителен log_without_warning ();
    
    // Аргументы функции
    void f ([[возможно_unused]] int a, int b);
    

    Пример:

    #include <iostream>
    #include <string>
    int main()
    {
    // Set debug mode in compiler or 'R'
    [[maybe_unused]] char mg_brk = 'D' ;
    // Compiler does not emit any warnings
    // or error on this unused variable
    }
  5. провалиться:
    [[fallthrough]] указывает, что провал в инструкции switch является преднамеренным. Отсутствие перерыва или возврата в операторе switch обычно считается ошибкой программиста, но в некоторых случаях провал может привести к очень сжатому коду, и поэтому он используется.

    Примечание. В отличие от других атрибутов, для провала требуется точка с запятой после объявления.

    Пример:

    void process_alert(Alert alert)
    {
    switch (alert) {
    case Alert::Red:
    evacuate();
    // Compiler emits a warning here
    // thinking it is done by mistake
    case Alert::Orange:
    trigger_alarm();
    // this attribute needs semicolon
    [[fallthrough]];
    // Warning suppressed by [[fallthrough]]
    case Alert::Yellow:
    record_alert();
    return ;
    case Alert::Green:
    return ;
    }
    }

    Предстоящие атрибуты C ++ 20

  6. вероятно: для оптимизации определенных операторов, которые имеют большую вероятность выполнения, чем другие. Вероятно, теперь доступен в последней версии компилятора GCC для экспериментальных целей.

    Пример

    int f( int i)
    {
    switch (i) {
    case 1:
    [[fallthrough]];
    [[likely]] case 2 : return 1;
    }
    return 2;
    }
  7. no_unique_address : указывает, что этот член данных не обязательно должен иметь адрес, отличный от всех других нестатических членов данных его класса. Это означает, что если класс состоит из пустого типа, компилятор может выполнить для него оптимизацию пустой базы.

    Пример:

    // empty class ( No state!)
    struct Empty {
    };
    struct X {
    int i;
    Empty e;
    };
    struct Y {
    int i;
    [[no_unique_address]] Empty e;
    };
    int main()
    {
    // the size of any object of
    // empty class type is at least 1
    static_assert( sizeof (Empty) >= 1);
    // at least one more byte is needed
    // to give ea unique address
    static_assert( sizeof (X) >= sizeof ( int ) + 1);
    // empty base optimization applied
    static_assert( sizeof (Y) == sizeof ( int ));
    }
  8. ожидает: он определяет условия (в форме контракта), которым должны соответствовать аргументы для выполнения конкретной функции.

    Использование:

    return_type func (args ...) [[ожидает: предварительное условие]]
    

    Пример:

    void list(node* n)[[expects:n != nullptr]]

    Нарушение контракта приводит к вызову обработчика нарушения или, если не указан, то std :: terminate ()

Разница между стандартными и нестандартными атрибутами

Стандартные атрибуты Нестандартные атрибуты
Уточнено стандартом и
Присутствуют во всех компиляторах
Предоставляется поставщиками компилятора
И присутствуют не во всех компиляторах
Код полностью переносимый
Без предупреждений и проблем
Хотя код становится переносимым (начиная с C ++ 17)
для нестандартных атрибутов в «стандартном синтаксисе»
некоторые предупреждения / ошибки все еще появляются в компиляторах.
Атрибуты записываются внутри
Стандартный синтаксис
[[atr]]
Некоторые атрибуты записываются внутри не
Стандартный синтаксис и другие написаны в компиляторе
конкретное ключевое слово, например declspec () или __attribute__
Стандартных атрибутов нет
В любом окружающем пространстве имен
Нестандартные атрибуты записываются в стандартном синтаксисе
С их охватывающим пространством имен
[[пространство имен :: attr]]

Изменения по сравнению с C ++ 11

  • Игнорирование неизвестных атрибутов:

    Начиная с C ++ 17, одно из основных изменений, внесенных в функцию атрибутов в C ++, касалось уточнения неизвестных атрибутов компилятором. В C ++ 11 или 14, если атрибут не распознавался компилятором, это приводило к ошибке и предотвращало компиляцию кода. В качестве обходного пути программисту пришлось удалить атрибут из кода, чтобы он заработал. Это создало серьезную проблему для переносимости. Кроме стандартных атрибутов, нельзя было использовать ни один из атрибутов, зависящих от производителя, поскольку код может сломаться. Это помешало фактическому использованию этой функции.

    В качестве решения стандарт сделал обязательным для всех компиляторов игнорирование атрибутов, которые не были ими определены. Это позволяло программистам свободно использовать атрибуты производителя в своем коде и обеспечивать переносимость кода. Большинство компиляторов, поддерживающих C ++ 17, теперь игнорируют неопределенные атрибуты и при обнаружении выдают предупреждение. Это позволяет программистам сделать код более гибким, поскольку теперь они могут указывать несколько атрибутов для одной и той же операции в пространствах имен разных поставщиков. (Поддержка: MSVC (НЕТ), GCC, CLANG (ДА))

    Пример:

    // Here the attributes will work on their respective
    [[msvc:: deprecated ]][[gnu:: deprecated ]] char * gets ( char * str) compilers
  • Использование пространств имен атрибутов без повторения :

    В C ++ 17 были смягчены некоторые правила использования «нестандартных» атрибутов. Одним из таких случаев является префикс пространств имен последующим нестандартным атрибутом. В C ++ 11 или 14, когда несколько атрибутов были записаны вместе, каждый из них должен был иметь префикс с их охватывающими пространствами имен, что привело к шаблону кода, как показано ниже.

    [[ gnu::always_inline, gnu:: const , gnu::hot, nodiscard ]] int f();

    Глядя на приведенный выше код, можно увидеть, что он кажется раздутым и загроможденным. Поэтому комитет решил «упростить случай использования нескольких атрибутов» вместе. На данный момент для программиста не обязательно снова и снова префикс пространства имен с последующими атрибутами, используемыми вместе. Это приводит к образцу кода, показанному ниже, который выглядит чистым и понятным.

    [[ using gnu: const , always_inline]] int f() { return 0; }
  • Несколько атрибутов в конкретном фрагменте кода :

    Теперь к определенному фрагменту кода на C ++ можно применить несколько атрибутов. В этом случае компилятор оценивает каждый из атрибутов в том порядке, в котором они записаны. Это позволяет программистам писать фрагменты кода, которые могут содержать несколько ограничений.

    Пример:

    #include <iostream>
    // Not implemented by compilers as of now
    // but will be implemented in the future
    [[nodiscard]] int f( int i)[[expects:i > 0]]
    {
    std::cout << " Always greater than 0!"
    << " and return val must "
    << "always be utilized" ;
    }

Разница между атрибутами в C ++ и C #

Существует заметная разница между атрибутами в C # и C ++. В случае C # программист может определять новые атрибуты, производные от System.Attribute ; тогда как в C ++ метаинформация фиксируется компилятором и не может использоваться для определения новых определяемых пользователем атрибутов. Это ограничение введено для предотвращения развития языка в новую форму, которая могла бы сделать язык более сложным.

Хотите узнать о лучших видео и практических задачах, ознакомьтесь с базовым курсом C ++ для базового и продвинутого уровня C ++ и курсом C ++ STL для базового уровня плюс STL. Чтобы завершить подготовку от изучения языка к DS Algo и многому другому, см. Полный курс подготовки к собеседованию .