Annotations (Attributes)

Depuis C++ 11, il est possible d'annoter certains passages de programmes C++ pour les complémenter avec un léger surcroît d'information. Ces annotations (en anglais, on utilise le terme Attributes).

Les annotations prennent la forme [[nom]] et permettent aux compilateurs de mieux faire leur travail, que ce soit en générant des avertissements plus clairs selon le contexte, en optimisant de manière plus efficace certains passages, ou simplement en clarifiant l'intention des programmeuses et des programmeurs.

L'idée derrière les annotations est que ce sont des énoncés complémentaires au programme : un compilateur doit pouvoir ne pas en tenir compte et demeurer capable de faire son travail. Ceci permet aux divers compilateurs d'offrir leurs annotations « maison » en plus de celles proposées à même le standard du langage.

Quelques exemples

Pour comprendre le rôle et l'utilité des annotations, voici quelques exemples simples, dont plusieurs (surtout ceux concernant des annotations de C++ 17) ont été empruntées à Jason Merrill.

Annotation [[noreturn]]

Depuis : C++ 11

Sur certains systèmes, particulièrement les systèmes embarqués où la mémoire n'est disponible qu'en quantité limitée, pouvoir générer un peu moins de code dans le cas de fonctions qui ne se compléteront jamais peut être fort utile. L'annotation [[noreturn]] informe un compilateur qu'il peut tenir compte de cette réalité et alléger le code généré lorsque le contexte s'y prête.

void critical_loop [[noreturn]] () {
   for(;;) {
      // ... code sans return
      // (throw est permis)
   }
}

Annotation [[carries_dependency]]

Depuis : C++ 11

Celle-ci est un peu obscure. Dans un programme utilisant des variables atomiques avec modèle memory_order_consume, où les transformations permises par les optimiseurs reposent sur sa compréhension des dépendances entre les données (Data Dependency), passer une adresse en paramètre à une fonction peut en faire perdre la trace par l'optimiseur, réduisant la qualité du code généré. L'annotation d'un tel paramètre par [[carries_dependency]] indique au compilateur qu'il vaut la peine de suivre la trace de ce paramètre de plus près.

void print(int *p) {
   cout << *p << endl;
}
void print2(int* [[carries_dependency]] p) {
   cout << *p << endl;
}
// ...
atomic<int*> p;
int* local=p.load(memory_order_consume);
// dépendance visible
if(local)
   cout << *local<< endl;
// dépendance opaque
if(local)
   print(local);
// dépendance visible
if(local)
   print2(local);

Annotation [[deprecated]]

Depuis : C++ 14

L'annotation [[deprecated]] permet d'indiquer qu'une fonction ou un type est sur le point d'être mis hors-service. Il est possible d'accompagner cette annotation d'un message, par [[deprecated("message")]], pour que le compilateur puisse relayer ce message lors de la génération des avertissements (ceci peut permettre de proposer un remplacement à ce qui est en cours de dépréciation).

[[deprecated]] int f() { return 3; }
[[deprecated("g() est obsolete, preferez trois()")]]
int g() { return 3; }
int trois() { return 3; }
int main() {
   f(); // message generique d'obsolescence
   g(); // message personnalise
   trois(); // pas de message
}

Annotation [[fallthrough]]

Depuis : C++ 17

L'annotation [[fallthrough]] informe le compilateur de l'absence volontaire d'un break dans une sélective (un switch). En effet, certains compilateurs (et plusieurs entreprises!) estiment que l'absence d'un break dans ces circonstances est source d'erreurs et de confusion, mais il se trouve qu'omettre le break peut être une saine pratique. Pour cette raison, [[fallthrough]] est une sorte de commentaire destiné au compilateur et l'informant du fait que cette omission est délibérée.

void f(char c) {
   switch(std::toupper(c, std::locale{""}) {
   case 'A': [[fallthrough]];
   case 'E': [[fallthrough]];
   case 'I': [[fallthrough]];
   case 'O': [[fallthrough]];
   case 'U': [[fallthrough]];
   case 'Y': traiter_voyelle(c);
   default:
      traiter_non_voyelle(c);
   }
}

Annotation [[nodiscard]]

Depuis : C++ 17

L'annotation [[nodiscard]] informe le compilateur du fait que le code client d'une fonction doit tenir compte de sa valeur de retour. Cette annotation peut décorer un type entier ou une fonction tout simplement.

class [[nodiscard]] Resultat { /* ... */ };
Resultat f();
int main() {
   f(); // provoquerait un avertissement
}

Annotation [[maybe_unused]]

Depuis : C++ 17

L'annotation [[maybe_unused]] informe le compilateur qu'une fonction, une variable, un paramètre... peut ne pas être utilisé en pratique, et qu'il ne s'agit pas d'un accident.

void f(int a, [[maybe_unused]] int b) {
   // ici, ne pas utiliser a peut générér un avertissement
   // mais ne pas utiliser b pourrait ne pas en générer
}

Annotation [[optimize_for_synchronized]]

« Depuis » : la spécification technique de mémoire transactionnelle

L'annotation [[optimize_for_synchronized]] informe le compilateur qu'une fonction peut être optimisée agressivement lorsqu'elle est appelée depuis un bloc synchronized. Des exemples sont disponibles sur http://en.cppreference.com/w/cpp/language/transactional_memory

Lectures complémentaires

Quelques liens pour enrichir le propos.


Valid XHTML 1.0 Transitional

CSS Valide !