atributos en C++

Los atributos son una de las características clave del C++ moderno que permite al programador especificar información adicional al compilador para hacer cumplir las restricciones (condiciones), optimizar ciertas piezas de código o generar algún código específico . En términos simples, un atributo actúa como una anotación o una nota para el compilador que proporciona información adicional sobre el código con fines de optimización y para hacer cumplir ciertas condiciones. Presentados en C++ 11, se han mantenido como una de las mejores características de C++ y evolucionan constantemente con cada nueva versión de C++.

Sintaxis:

// C++11
[[lista-de-atributos]]

// C++17
[[utilizando espacio de nombres de atributos: lista de atributos]]

// Próximo C++20
[[contrato-atributo-token identificador de nivel de contrato: expresión]]

Excepto algunos específicos, la mayoría de los atributos
se pueden aplicar con variables, funciones, clases,
estructuras, etc.

Propósito de los atributos en C++

  • Para hacer cumplir las restricciones en el código:

    Aquí restricción se refiere a una condición que los argumentos de una función en particular deben cumplir para su ejecución (precondición). En versiones anteriores de C++, el código para especificar restricciones se escribía de esta manera

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

    Aumenta la legibilidad de su código y evita el desorden que se escribe dentro de la función para la verificación de argumentos.

    int f(int i)[[expects:i > 0]]
    {
        // Code
    }
  • Para dar información adicional al compilador con fines de optimización:

    Los compiladores son muy buenos en la optimización, pero en comparación con los humanos, todavía se quedan atrás en algunos lugares y proponen un código generalizado que no es muy eficiente. Esto sucede principalmente debido a la falta de información adicional sobre el “problema” que tienen los humanos. Para reducir este problema hasta cierto punto, el estándar C++ ha introducido algunos atributos nuevos que permiten especificar un poco más al compilador en lugar de la declaración del código en sí. Una vez que tal ejemplo es el de probable.

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

    Cuando la declaración está precedida por probable, el compilador realiza optimizaciones especiales con respecto a esa declaración, lo que mejora el rendimiento general del código. Algunos ejemplos de tales atributos son [carries_dependency], [probable], [improbable]

  • Suprimiendo ciertas advertencias y errores que el programador pretendía tener en su código:

    Ocurre rara vez, pero a veces el programador intenta intencionalmente escribir un código defectuoso que el compilador detecta y lo informa como un error o una advertencia. Un ejemplo de ello es el de una variable no utilizada que se ha dejado en ese estado por una razón específica o de una declaración de cambio donde las declaraciones de ruptura no se colocan después de algunos casos para dar lugar a condiciones de caída. Para eludir errores y advertencias en dichas condiciones, C++ proporciona atributos como [maybe_unused] y [fallthrough] que evitan que el compilador genere advertencias o errores.

    #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
    }

Lista de atributos estándar en C++

    C++11

  1. noreturn: indica que la función no devuelve un valor

    Uso:

    [[noreturn]] void f();
    

    Al mirar el código anterior, surge la pregunta: ¿cuál es el punto de no tener devolución cuando el tipo de devolución es en realidad nulo ? Si una función tiene un tipo vacío, entonces en realidad regresa a la persona que llama sin un valor, pero si el caso es tal que la función nunca regresa a la persona que llama (por ejemplo, un ciclo infinito), entonces agregar un atributo sin retorno le da pistas al compilador . para optimizar el código o generar mejores avisos.

    Ejemplo:

    #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();
    }

    Advertencia:

    main.cpp: In function 'void f()':
    main.cpp:8:1: warning: 'noreturn' function does return
     }
     ^
    
  2. C++14

  3. obsoleto: Indica que el nombre o la entidad declarada con este atributo se ha vuelto obsoleto y no debe usarse por alguna razón específica. Este atributo se puede aplicar a espacios de nombres, funciones, estructuras de clases o variables.

    Uso:

    [[deprecated("Reason for deprecation")]]
    
    // For Class/Struct/Union
    struct [[deprecated]] S;
    
    // For Functions
    [[deprecated]] void f();
    
    // For namespaces
    namespace [[deprecated]] ns{}
    
    // For variables (including static data members)
    [[deprecated]] int x;
    
    

    Ejemplo:

    #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);
    }

    Advertencia:

    main.cpp: In function 'int main()':
    main.cpp:26:9: warning: 'void gets(char*)' is deprecated:
     Susceptible to buffer overflow [-Wdeprecated-declarations]
       gets(a);
             ^
    
  4. C++17

  5. nodiscard: las entidades declaradas con nodiscard no deben tener sus valores devueltos ignorados por la persona que llama. Simplemente diga que si una función devuelve un valor y está marcada como no descartar, entonces la persona que llama debe utilizar el valor devuelto y no descartarlo.

    uso:

    // Functions
    [[nodiscard]] void f();
    
    // Class/Struct declaration 
    struct [[nodiscard]] my_struct{};
    

    La principal diferencia entre nodiscard con funciones y nodiscard con declaración de estructura/clase es que, en el caso de función, nodiscard se aplica solo a esa función en particular que se declara sin descarte, mientras que en el caso de declaración de clase/estructura, nodiscard se aplica a cada función que devuelve el no descartar objeto marcado por valor.

    Ejemplo:

    #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;
    }

    Advertencia:

    prog.cpp:5:21: warning: 'nodiscard' attribute directive ignored [-Wattributes]
     [[nodiscard]] int f()
                         ^
    prog.cpp:10:20: warning: 'nodiscard' attribute directive ignored [-Wattributes]
     class[[nodiscard]] my_class{};
                        ^
    
  6. may_unused : se usa para suprimir advertencias sobre cualquier entidad no utilizada (por ejemplo: una variable no utilizada o un argumento no utilizado para una función).

    Uso:

    //Variables
    [[maybe_used]] bool log_var = true;
    
    //Functions
    [[maybe_unused]] void log_without_warning();
    
    //Function arguments 
    void f([[maybe_unused]] int a, int b);
    

    Ejemplo:

    #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
    }
  7. fallthrough:
    [[fallthrough]] indica que una falla en una declaración de cambio es intencional. La falta de un descanso o retorno en una declaración de cambio generalmente se considera un error del programador, pero en algunos casos, la falla puede resultar en un código muy conciso y, por lo tanto, se usa.

    Nota: A diferencia de otros atributos, una falla requiere un punto y coma después de declararse.

    Ejemplo:

    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;
        }
    }
  8. Próximos atributos de C++20

  9. probable: para la optimización de ciertas declaraciones que tienen más probabilidad de ejecutarse que otras. Likely ahora está disponible en la última versión del compilador GCC para fines de experimentación.

    Ejemplo

    int f(int i)
    {
        switch (i) {
        case 1:
            [[fallthrough]];
            [[likely]] case 2 : return 1;
        }
        return 2;
    }
  10. no_unique_address : indica que este miembro de datos no necesita tener una dirección distinta de todos los demás miembros de datos no estáticos de su clase. Esto significa que si la clase consta de un tipo vacío, el compilador puede realizar una optimización de base vacía en él.

    Ejemplo:

    // 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 e a unique address
        static_assert(sizeof(X) >= sizeof(int) + 1);
      
        // empty base optimization applied
        static_assert(sizeof(Y) == sizeof(int));
    }
  11. expects: Especifica las condiciones (en forma de contrato) que los argumentos deben cumplir para que se ejecute una función en particular.

    Uso:

    return_type func ( args...) [[expects : precondition]]
    

    Ejemplo:

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

    La violación del contrato da como resultado la invocación del controlador de violación o, si no se especifica, entonces std::terminate()

Diferencia entre atributos estándar y no estándar

Atributos estándar Atributos no estándar
Especificado por el estándar y
Están presentes en todos los compiladores
Proporcionado por los proveedores de compiladores
y no está presente en todos los compiladores
El código es completamente portátil
Sin advertencias ni problemas
Aunque el código se vuelve portátil (desde C++ 17)
para atributos no estándar en «sintaxis estándar»,
algunas advertencias/errores aún aparecen en los compiladores.
Los atributos se escriben dentro de la
sintaxis estándar
[[atr]]
Algunos de los atributos están escritos dentro de la
sintaxis no estándar y otros están escritos dentro de una
palabra clave específica del compilador como declspec() o __attribute__
Los atributos estándar no están presentes
en ningún espacio de nombres adjunto
Los atributos no estándar se escriben en sintaxis estándar
con su espacio de nombres adjunto
[[namespace::attr]]

Cambios desde C++11

  • Ignorando atributos desconocidos:

    Desde C++ 17, uno de los principales cambios introducidos para la función de atributo en C++ fue la aclaración de atributos desconocidos por parte del compilador. En C++ 11 o 14, si el compilador no reconocía un atributo, se producía un error y se impedía la compilación del código. Como solución temporal, el programador tuvo que eliminar el atributo del código para que funcionara. Esto introdujo un problema importante para la portabilidad. Aparte de los atributos estándar, no se podría utilizar ninguno de los atributos específicos del proveedor, ya que el código se rompería. Esto impidió el uso real de esta función.

    Como solución, el estándar obligaba a todos los compiladores a ignorar los atributos que no estaban definidos por ellos. Esto permitió a los programadores usar libremente atributos específicos del proveedor en su código y asegurarse de que el código aún fuera portátil. La mayoría de los compiladores que admiten C++ 17 ahora ignoran los atributos indefinidos y generan una advertencia cuando se encuentran. Esto permite a los programadores hacer que el código sea más flexible, ya que ahora pueden especificar varios atributos para la misma operación en espacios de nombres de diferentes proveedores. (Soporte: MSVC (AÚN NO), GCC, CLANG (SÍ))

    Ejemplo:

    // Here the attributes will work on their respective
    [[msvc::deprecated]][[gnu::deprecated]] char* gets(char* str) compilers
  • Uso de espacios de nombres de atributos sin repetición :

    En C++ 17, se relajaron algunas de las reglas con respecto al uso de atributos «no estándar». Uno de esos casos es el de prefijar espacios de nombres con un atributo no estándar posterior. En C++ 11 o 14, cuando varios atributos se escribieron juntos, cada uno de ellos tenía que tener un prefijo con sus espacios de nombres adjuntos, lo que dio lugar al patrón de código que se muestra a continuación.

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

    Mirando el código anterior, se puede ver que parece hinchado y desordenado. Entonces, el comité decidió «simplificar el caso cuando se usan múltiples atributos» juntos. A partir de ahora, no es obligatorio que el programador agregue un prefijo al espacio de nombres una y otra vez y que los atributos subsiguientes se usen juntos. Esto da lugar al patrón de código que se muestra a continuación, que parece limpio y comprensible.

    [[using gnu:const, always_inline]] int f() { return 0; }
  • Múltiples atributos sobre una pieza particular de código :

    Ahora se pueden aplicar varios atributos a una determinada pieza de código en C++. El compilador, en ese caso, evalúa cada uno de los atributos en el orden en que se escriben. Esto permite a los programadores escribir piezas de código que pueden contener múltiples restricciones.

    Ejemplo:

    #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";
    }

Diferencia entre atributos en C++ y C#

Hay una diferencia notable entre los atributos en C# y C++. En el caso de C#, el programador puede definir nuevos atributos derivando de System.Attribute ; mientras que en C++, el compilador fija la metainformación y no se puede usar para definir nuevos atributos definidos por el usuario. Esta restricción se coloca para evitar que el idioma evolucione a una nueva forma que podría haberlo complicado.

Publicación traducida automáticamente

Artículo escrito por Gopi_Krishna_Menon y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *