Fonctionnalités C++ modernes : les attributs

Arne Mertz

Traducteur

MaximeCh

La standardisation grandissante des attributs permet d’écrire un code plus clair, pour les humains, comme pour les compilateurs et autres outils.

Les attributs sont une façon standard de gérer les extensions jusqu’à présent non standards des compilateurs.

Pas clair ? Jetons un œil au fonctionnement de certaines extensions de compilateurs.

Mis à part les extensions de grande envergure qui chamboulent complètement le langage, nombre de compilateurs permettent d’ajouter des annotations aux fonctions, variables, classes, etc.

Souvent, ces extensions sont de purs ajouts : en les enlevant, on retrouve du C++ tout ce qu’il y a de plus classique.

Les identifiants contenant des underscores doubles sont courants, car ils sont réservés à l’implémentation : pas de risque de conflit en portant du C++ standard vers un autre compilateur.

Un exemple est l’annotation __fastcall de MSVC qui indique au compilateur de passer les deux premiers arguments – pourvu qu’ils prennent une taille maximum donnée, en registre, sur les architectures x86.

L’équivalent chez GCC requiert d’écrire __attribute__((fastcall)).

Le problème apparaît déjà : aucune standardisation des extensions compilateur n’existe avant C++11.

Deux compilateurs différents utiliseront des annotations différentes, ce qui rend le code non portable, à moins d’utiliser des macros de préprocesseur. Commentez

- Titre : Fonctionnalités C++ modernes : les attributs

- Auteur : Arne Mertz

- Traducteur : MaximeCh

- Parution : 22 avril 2019

- Licence : Copyright ® 2019 Arne Mertz. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Attributs en C++11

- II. Attributs standards

- III. Conclusion

- IV. Remerciements

I - Attributs en C++11

Voilà qui a changé avec le C++11 : les attributs sont désormais portables en les écrivant entre doubles crochets :

[[xyz]] void foo(int i, int j) {
//...
}

Malheureusement, ça ne résout pas le problème général : seuls quelques attributs sont standardisés.
Les compilateurs sont toujours autorisés à utiliser leurs propres attributs, mais ils doivent se situer dans leur espace de nom. Par exemple, l’attribut fastcall de GCC peut s’écrire de façon standard : [[gnu::fastcall]], et les autres compilateurs ne sont pas tenus de le comprendre.

Jusqu’à C++17, les autres compilateurs peuvent même refuser la compilation en rencontrant un attribut inconnu. Depuis C++17, ils doivent les ignorer. Il semble cependant que MSVC 2015 ne supporte pas du tout la nouvelle syntaxe des attributs…

II - Attributs standards

Conformité des compilateurs au standard mise à part, certains attributs ont été standardisés.
Voici les plus importants pour la maintenabilité :

- [[deprecated]], [[deprecated("Because it's old")]]
Cet attribut permet de marquer des fonctions, variables, classes… qui ne doivent plus être utilisées, car elles seront retirées du code à l’avenir.
Déprécier des éléments de la sorte peut être particulièrement pratique si l’on est en train de nettoyer un code, mais que les retirer purement et simplement n’est pas faisable à cause de leur couplage fort avec l’existant.

- [[fallthrough]]
Cet attribut à la fin d’un case dans un switch signale que le mot-clé break a été volontairement omis. Sans ça, les analyseurs statiques et les relecteurs pourraient alerter sur le break manquant : cela précise à tout le monde que vous savez ce que vous faites.

switch(something) {
case SPECIAL_CASE:
prepareForSpecialCase();
[[fallthrough]]
default:
handleAllCases();
}

- [[nodiscard]]
Cet attribut demande au compilateur d’avertir si, lors de l’appel d’une fonction qui possède l’attribut, ou dont le type de retour le possède, la valeur de retour est ignorée. Une valeur de retour est ignorée quand elle n’est pas affectée à une variable : elle est typiquement appelée comme simple expression, ou à gauche d’un opérateur virgule. L’avertissement peut être supprimé avec un cast explicite de la valeur en void.

struct [[nodiscard]] X {
int i;
};

X foo() {
return {42};
}

[[nodiscard]] int bar() {
return 3;
}

void moo() {
foo(); //Warning: discarded X
auto i = (bar(), 55); //Warning: discarded bar()
(void)bar(); //OK
}

- [[maybe_unused]]
En pratique l’inverse de [[nodiscard]] : de nombreux compilateurs avertissent déjà des valeurs non usitées dans certains cas. [[maybe_unused]] supprime cet avertissement sur celles déclarées avec cet attribut.

- [[noreturn]]
S’applique uniquement aux déclarations de fonctions, et précise que la fonction ne retournera pas ; c.-à-d. que la fonction soit lancera une exception, soit appellera exit ou terminate dans toutes ses branches d’exécution. Principalement utilisé pour l’optimisation, il tient aussi lieu d’un peu de documentation.

III - Conclusion

L’implémentation des attributs, tout comme leur standardisation, sont loin d’être terminées et suivent leur cours.
Ce qui existe déjà peut être utile et devrait être utilisé à bon escient.

Pour plus d’informations sur les attributs, je vous recommande ce billet de Bartlomiej Filipek !

IV - Remerciements

Nous remercions Arne Mertz qui nous a aimablement autorisés à traduire ce tutoriel. Nos remerciements également à MaximeCh pour la traduction, Winjerome et Luc Hermitte pour la relecture technique et la mise au gabarit DVP, f-leb pour la correction orthographique.