Après la présentation générale de cette nouvelle norme, le premier véritable article sur les fonctionnalités du C++ traite des nouveautés concernant les attributs.
Rappels
Les attributs, introduits dans le langage avec la norme C++11, constituent un moyen d’annoter certains éléments du langage. On peut les voir comme un sur-ensemble des annotations Java et une syntaxe standard pour les extensions de compilateurs.
La syntaxe permet d’annoter des éléments du langages (telles que les variables, les fonctions, les instructions…) en spécifiant un attribut.
Les attributs peuvent recevoir des paramètres, [[deprecated]] par exemple, permettant de signaler un élément déprécié depuis , autorise l’ajout d’un message en argument :
1 2 3 4 5 6 7 8 9 10 11 12 |
struct test { [[deprecated]] void foo() {} [[deprecated("version const")]] void foo() const {} }; int main() { test t; t.foo(); // GCC-7.2 : warning: 'void test::foo()' is deprecated [-Wdeprecated-declarations] const test& r{ t }; r.foo(); // GCC-7.2 : warning: 'void test::foo() const' is deprecated: version const [-Wdeprecated-declarations] } |
Dans sa FAQ, Bjarne Stroustrup présente exemple d’attribut qui pourrait être utilisé pour la bibliothèque de programmation parallèle OpenMP permettant de remplacer la syntaxe actuelle à base de pragma.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Code actuel #pragma omp parallel for for(int i = 0; i < v.size(); ++i) { ... } // Exemple possible avec les attributs for [[omp::parallel()]] (int i = 0; i < v.size(); ++i) { // ... } |
Les attributs constituent une fonctionnalité jeune, mais nous verrons certainement de nombreux nouveaux attributs, standards ou non, arriver dans les normes à venir.
Les nouveaux attributs
C++11 avait ajouté 2 attributs, [[noreturn]] et [[carries_dependency]] , complétés par [[deprecated]] en C++14. Le C++17 propose quant à lui 3 nouveaux attributs : [[falltrought]], [[maybe_unused]] et [[nodiscard]].
L’attribut [[fallthrough]]
L’attribut [[fallthrough]] est destiné aux instructions switch. L’objectif est de préciser au compilateur qu’une absence de saut du flot de contrôle est volontaire (que ce soit un break ou un return).
Les compilateurs signalent souvent les fallthroughs, c’est à dire lorsque le programme va passer d’une case à l’autre sans saut. Si il s’agit en effet d’une erreur de programmation assez classique, il est également possible que celle-ci soit intentionnelle.
[[fallthrough]] indique au compilateur que le fallthrough est intentionnel et ne doit pas être considéré comme une erreur. Il permet ainsi d’éviter un warning de la part du compilateur. Celui-ci doit précéder un case.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
action handle_event(event my_event) { switch(my_event) { case event::mouse_click: [[fallthrough]]; // fallthrough explicite, pas de warning. case event::mouse_wheel: auto mouse_button = get_mouse_button(); return action(action::event::mouse, mouse_button); case event::button_click: auto& b = get_clicked_button(); // fallthrough : warning possible du compilateur. default: return action(action::event::unknown); } } |
L’attribut [[maybe_unused]]
C++17 propose aussi l’attribut [[maybe_unused]] permettant de signaler au compilateur qu’une variable peut être inutilisée et qu’il n’y a pas lieu de s’inquiéter. Le compilateur ne signalera pas d’avertissement si la variable est effectivement inutilisée.
1 2 3 4 5 6 |
[[maybe_unused]] void f([[maybe_unused]] bool thing1, [[maybe_unused]] bool thing2) { [[maybe_unused]] bool b = thing1 && thing2; assert(b); } |
Dans l’exemple précédant, honteusement pompé sur cppreference.com, l’attribut est appliqué à plusieurs entités.
Ici, dans le cas d’une compilation en mode release, la macro assert va disparaître et par conséquent la variable b sera inutilisée. Puisque l’attribut [[maybe_unused]] est spécifié, le compilateur ne générera pas d’avertissement.
Par ricochet, les arguments de f seront eux aussi inutilisés, d’où l’application de l’attribut à nouveau. L’attribut est également applicable aux fonctions et aux classes.
Petite précision, un élément est marqué comme [[maybe_unused]] , à partir du moment où le compilateur rencontre une déclaration qui le spécifie comme tel, comme le précise le standard : “A name or entity declared without the maybe_unused attribute can later be redeclared with the attribute and vice versa. An entity is considered marked after the first declaration that marks it.” ([dcl.attr.unused] N4606).
L’attribut [[nodiscard]]
Un poil différent des deux nouveaux attributs précédents, qui jouent sur le contrôle des avertissements du compilateur, l’attribut [[nodiscard]] permet de refuser le droit du programmeur d’ignorer le retour d’une fonction.
Assez simplement, une fonction avec retour peut être marquée par [[nodiscard]] ce qui indique au compilateur que le retour ne devrait pas être ignoré :
1 2 3 4 5 6 |
[[nodiscard]] error_code f(); int main() { f(); // GCC-7.2 : warning: ignoring return value of 'error_code f()', // declared with attribute nodiscard [-Wunused-result] } |
L’attribut peut également être appliqué à un type. Dans ce cas, toutes les fonctions retournant ce type sont implicitement marquée [[nodiscard]].
1 2 3 4 5 6 7 8 9 |
struct [[nodiscard]] error_code {}; error_code f(); error_code g(); int main() { f(); // warning [[nodiscard]]. g(); // warning [[nodiscard]]. } |
Notez que j’ai tenté de voir si le marquage est hérité, mais il semble que ce ne soit pas le cas (quelqu’un de sérieux aurait vérifié dans la norme, mais bon…), en tout cas pour GCC :
1 2 3 4 5 6 7 8 |
struct [[nodiscard]] error_code{}; struct critical_error_code : error_code {}; critical_error_code h(); int main() { h(); // aucun warning. } |
Changements sur les attributs
En plus des trois nouveaux attributs, le C++17 modifie également leur fonctionnement général.
Premièrement, les attributs sur les espaces de nom sont désormais autorisés :
1 2 3 |
namespace [[attribute]] A { /* ... */ } |
De même, le standard autorise aussi les attributs pour les énumérateurs :
1 2 3 4 5 |
enum type { blue[[beautiful]], red, yellow[[ugly]] }; |
Dernier ajout pour les attributs, la possibilité d’utiliser la directive using pour importer un espace de nom dans une directive d’attribut.
1 2 3 4 5 |
[[fs::admin(1), fs::debug, fs::r, fs::w, fs::path("/var/f.txt")]] void fopen(); [[using fs: admin(1), debug, r, w, path("/var/f.txt")]] void fopen(); |
Dans l’exemple précédent, les attributs appartiennent tous à l’espace de nom fs, la directive using permet d’importer l’espace de nom de manière à ne pas avoir à le répéter.
Notez que contrairement au célèbre using namespace , l’import de l’espace de nom ne se fait que dans l’attribut courant, ce qui n’implique pas les problèmes de contamination du code et de conflits comme les usages maladroits d' using namespace.
Le C++17 stipule également que les attributs inconnus doivent être ignorés par le compilateur.
1 2 |
// Le compilateur doit ignorer cet attribut s'il ne le reconnait pas. [[SpecificAttributeNamespace::foo("foo", 1)]] void foo(); |
Merci d’avoir lu cet article, et à bientôt 🙂
Références et ressources
- [EN] open-std.org | p0283r2 “Standard and non-standard attributes”
- [EN] open-std.org | n4266 “Attributes for namespaces and enumerators”
- [EN] open-std.org | p0028r4 “Using attribute namespaces without repetition”
- [EN] cppreference.com – attribute specifier sequence
- [EN] open-std.org | p0188r1 “Wording for [[fallthrough]] attribute.”
- [EN] cppreference.com – C++ attribute: fallthrough
- [EN] open-std.org | p0189r1 “Wording for [[nodiscard]] attribute.”
- [EN] cppreference.com – C++ attribute: nodiscard
- [EN] open-std.org | p0212r1 “Wording for [[maybe_unused]] attribute.”
- [EN] cppreference.com – C++ attribute: maybe_unused
You must log in to post a comment.