Accolades inutiles en C ++?

En faisant une revue de code pour un collègue aujourd’hui, j’ai vu une chose particulière. Il avait entouré son nouveau code d’accolades comme ceci:

Constructor::Constructor() { existing code { New code: do some new fancy stuff here } existing code } 

Quel en est le résultat, le cas échéant? Quelle pourrait être la raison de faire cela? D’où vient cette habitude?

Modifier:

Sur la base des commentaires et de quelques questions ci-dessous, j’estime que je dois en append à la question, même si j’ai déjà marqué une réponse.

L’environnement est des périphériques intégrés. Il y a beaucoup de code C hérité enveloppé dans des vêtements C ++. Il y a beaucoup de développeurs C ++ tournés vers C.

Il n’y a pas de sections critiques dans cette partie du code. Je l’ai seulement vu dans cette partie du code. Il n’y a pas d’allocation de mémoire majeure effectuée, mais uniquement des indicateurs définis, et un peu de twiddling.

Le code qui est entouré d’accolades est quelque chose comme:

 { bool isInit; (void)isStillInInitMode(&isInit); if (isInit) { return isInit; } } 

(Ne vous souciez pas du code, tenez-vous-en aux accolades …;)) Après les accolades, il y a encore quelques modifications, vérifications d’état et signalisation de base.

J’ai parlé au gars et sa motivation était de limiter la scope des variables, de nommer les conflits et d’autres choses que je ne pouvais pas vraiment relever.

De mon sharepoint vue, cela semble plutôt étrange et je ne pense pas que les accolades devraient être dans notre code. J’ai vu quelques bons exemples dans toutes les réponses sur pourquoi on pouvait entourer le code d’accolades, mais ne devriez-vous pas séparer le code en méthodes plutôt?

C’est parfois sympa car cela vous donne une nouvelle scope, où vous pouvez déclarer plus “proprement” de nouvelles variables (automatiques).

En C++ ce n’est peut-être pas très important car vous pouvez introduire de nouvelles variables n’importe où, mais peut-être que l’habitude vient de C , où vous ne pouvez pas le faire avant C99. 🙂

Comme C++ a des destructeurs, il peut aussi être utile d’avoir des ressources (fichiers, mutex, etc.) libérées automatiquement à mesure que le domaine se ferme, ce qui peut rendre les choses plus propres. Cela signifie que vous pouvez conserver une ressource partagée pour une durée plus courte que si vous l’aviez saisie au début de la méthode.

Un objective possible est de contrôler la scope des variables . Et comme les variables avec stockage automatique sont détruites lorsqu’elles sont hors de scope, cela peut également permettre à un destructeur d’être appelé plus tôt.

Les accolades supplémentaires sont utilisées pour définir la scope de la variable déclarée à l’intérieur des accolades. Cela est fait pour que le destructeur soit appelé quand la variable sort de la scope. Dans le destructeur, vous pouvez libérer un mutex (ou toute autre ressource) pour que l’autre puisse l’acquérir.

Dans mon code de production, j’ai écrit quelque chose comme ceci:

 void f() { //some code - MULTIPLE threads can execute this code at the same time { scoped_lock lock(mutex); //critical section starts here //critical section code //EXACTLY ONE thread can execute this code at a time } //mutex is automatically released here //other code - MULTIPLE threads can execute this code at the same time } 

Comme vous pouvez le voir, de cette façon, vous pouvez utiliser scoped_lock dans une fonction et, en même temps, définir sa scope en utilisant des accolades supplémentaires. Cela garantit que même si le code en dehors des accolades supplémentaires peut être exécuté par plusieurs threads simultanément, le code à l’intérieur des accolades sera exécuté par exactement un thread à la fois.

Comme d’autres l’ont souligné, un nouveau bloc introduit une nouvelle étendue, permettant d’écrire un peu de code avec ses propres variables qui ne nuisent pas à l’espace de noms du code environnant, et n’utilise pas les ressources plus longtemps que nécessaire.

Cependant, il y a une autre bonne raison de le faire.

Il s’agit simplement d’isoler un bloc de code qui atteint un objective (sous) particulier. Il est rare qu’une seule déclaration réalise un effet de calcul que je souhaite; habituellement il en faut plusieurs. Les placer dans un bloc (avec un commentaire) me permet de dire au lecteur (souvent moi-même plus tard):

  • Ce morceau a un but conceptuel cohérent
  • Voici tout le code nécessaire
  • Et voici un commentaire sur le morceau.

par exemple

 { // update the moving average i= (i+1) mod ARRAYSIZE; sum = sum - A[i]; A[i] = new_value; sum = sum + new_value; average = sum / ARRAYSIZE ; } 

Vous pourriez argumenter que je devrais écrire une fonction pour faire tout cela. Si je ne le fais qu’une fois, l’écriture d’une fonction ajoute simplement de la syntaxe et des parameters supplémentaires; il semble peu de chose. Pensez à ceci comme une fonction anonyme sans paramètre.

Si vous avez de la chance, votre éditeur aura une fonction de pliage / dépliage qui vous permettra même de cacher le bloc.

Je fais ça tout le temps. C’est un grand plaisir de connaître les limites du code que j’ai besoin d’inspecter, et encore mieux de savoir que si ce morceau n’est pas celui que je veux, je n’ai pas à regarder les lignes.

Une des raisons pourrait être que la durée de vie des variables déclarées dans le nouveau bloc d’accolades est limitée à ce bloc. Une autre raison qui vient à l’esprit est de pouvoir utiliser le pliage de code dans l’éditeur favori.

C’est la même chose qu’un bloc if (ou while etc ..), simplement sans if . En d’autres termes, vous introduisez une scope sans introduire de structure de contrôle.

Cette “scope explicite” est généralement utile dans les cas suivants:

  1. Pour éviter les conflits de noms.
  2. À la scope en using .
  3. Pour contrôler quand les destructeurs sont appelés.

Exemple 1:

 { auto my_variable = ... ; // ... } // ... { auto my_variable = ... ; // ... } 

Si my_variable s’avère être un nom particulièrement bon pour deux variables différentes utilisées séparément, la scope explicite vous permet d’éviter d’inventer un nouveau nom uniquement pour éviter le conflit de noms.

Cela vous permet également d’éviter d’utiliser my_variable hors de la scope prévue par accident.

Exemple 2:

 namespace N1 { class A { }; } namespace N2 { class A { }; } void foo() { { using namespace N1; A a; // N1::A. // ... } { using namespace N2; A a; // N2::A. // ... } } 

Les situations pratiques lorsque cela est utile sont rares et peuvent indiquer que le code est prêt pour le refactoring, mais le mécanisme existe déjà si vous en avez réellement besoin.

Exemple 3:

 { MyRaiiClass guard1 = ...; // ... { MyRaiiClass guard2 = ...; // ... } // ~MyRaiiClass for guard2 called. // ... } // ~MyRaiiClass for guard1 called. 

Cela peut être important pour RAII dans les cas où le besoin de libérer des ressources ne «tombe» naturellement pas sur les limites des fonctions ou des structures de contrôle.

Ceci est vraiment utile lorsque vous utilisez des verrous de scope en conjonction avec des sections critiques dans la programmation multithread. Votre verrou de scope initialisé dans les accolades (généralement la première commande) sera hors de scope à la fin de la fin du bloc et les autres threads pourront être réexécutés.

Tous les autres ont déjà traité correctement les possibilités de cadrage, RAII, etc., mais comme vous mentionnez un environnement intégré, il existe une autre raison possible:

Peut-être le développeur ne fait-il pas confiance à l’allocation de registre de ce compilateur ou souhaite-t-il contrôler explicitement la taille des trames de la stack en limitant le nombre de variables automatiques dans la scope à la fois.

Ici, isInit sera probablement sur la stack:

 { bool isInit; (void)isStillInInitMode(&isInit); if (isInit) { return isInit; } } 

Si vous supprimez les accolades, l’espace pour isInit peut être réservé dans le cadre de la stack même s’il peut potentiellement être réutilisé: s’il y a beaucoup de variables automatiques avec une étendue localisée similaire et que la taille de votre stack est limitée, cela pourrait poser problème .

De même, si votre variable est affectée à un registre, sortir du périmètre devrait fournir un indice fort indiquant que le registre est maintenant disponible pour une réutilisation. Vous devrez regarder l’assembleur généré avec et sans les accolades pour savoir si cela fait une réelle différence (et le profiler – ou surveiller les débordements de stack – pour voir si cette différence compte vraiment).

Je pense que d’autres ont déjà abordé la question de la scope, alors je mentionnerai que les accolades inutiles pourraient également servir à des fins de développement. Par exemple, supposons que vous travailliez sur une optimisation vers une fonction existante. Basculer l’optimisation ou tracer un bug sur une séquence d’instructions particulière est simple pour le programmeur – voir le commentaire précédant les accolades:

 // if (false) or if (0) { //experimental optimization } 

Cette pratique est utile dans certains contextes tels que le débogage, les périphériques intégrés ou le code personnel.

Je suis d’accord avec “ruakh”. Si vous voulez une bonne explication des différents niveaux de scope en C, consultez ce post:

Divers niveaux de scope dans l’application C

En général, l’utilisation de la “scope du bloc” est utile si vous souhaitez simplement utiliser une variable temporaire que vous n’avez pas besoin de suivre pendant toute la durée de l’appel de la fonction. De plus, certaines personnes l’utilisent pour que vous puissiez utiliser le même nom de variable à plusieurs endroits, mais ce n’est généralement pas une bonne idée. par exemple:

 int unusedInt = 1; int main(void) { int k; for(k = 0; k<10; k++) { int returnValue = myFunction(k); printf("returnValue (int) is: %d (k=%d)",returnValue,k); } for(k = 0; k<100; k++) { char returnValue = myCharacterFunction(k); printf("returnValue (char) is: %c (k=%d)",returnValue,k); } return 0; } 

Dans cet exemple particulier, j'ai défini returnValue deux fois, mais comme il est juste à scope de bloc, au lieu de la scope de la fonction (ie: la scope de la fonction serait, par exemple, de déclarer returnValue juste après int main (void)) obtenir des erreurs de compilation, car chaque bloc est inconscient de l'instance temporaire de returnValue déclarée.

Je ne peux pas dire que ce soit une bonne idée en général (c.-à-d. Que vous ne devriez probablement pas réutiliser les noms de variables à plusieurs resockets), mais en général, cela fait gagner du temps et vous évite de gérer valeur de returnValue dans toute la fonction.

Enfin, notez la scope des variables utilisées dans mon exemple de code:

 int: unusedInt: File and global scope (if this were a static int, it would only be file scope) int: k: Function scope int: returnValue: Block scope char: returnValue: Block scope 

Alors, pourquoi utiliser des accolades “inutiles”?

  • Pour fins de “cadrage” (comme mentionné ci-dessus)
  • Rendre le code plus lisible d’une manière (un peu comme utiliser #pragma ou définir des “sections” pouvant être visualisées)
  • Parce que vous pouvez. Aussi simple que cela.

PS Ce n’est pas un code mauvais; c’est 100% valide. Donc, c’est plutôt une question de goût (rare).

Après avoir visualisé le code dans l’édition, je peux dire que les crochets inutiles sont probablement (dans la vue du codeur d’origine) clairement à 100% de ce qui se passera pendant le if / then, même si ce n’est qu’une ligne maintenant. plus de lignes plus tard, et les crochets garantissent que vous ne ferez pas d’erreur.

 { bool isInit; (void)isStillInInitMode(&isInit); if (isInit) { return isInit; } return -1; } 

Si ce qui précède était original et que la suppression de “extras” entraînerait:

 { bool isInit; (void)isStillInInitMode(&isInit); if (isInit) return isInit; return -1; } 

alors, une modification ultérieure pourrait ressembler à ceci:

 { bool isInit; (void)isStillInInitMode(&isInit); if (isInit) CallSomethingNewHere(); return isInit; return -1; } 

et cela, bien sûr, causerait un problème, puisque maintenant, isInit serait toujours renvoyé, indépendamment du if / then.

Les objects sont détruits automatiquement lorsqu’ils sont hors de scope …

Un autre exemple d’utilisation est les classes liées à l’interface utilisateur, notamment Qt.

Par exemple, vous avez une interface utilisateur complexe et de nombreux widgets, chacun d’eux ayant son propre espacement, sa propre disposition, etc. Au lieu de les nommer space1, space2, spaceBetween, layout1, ... vous pouvez vous sauver des noms non descriptifs pour les variables qui n’existent que sur deux ou trois lignes de code.

Eh bien, certains pourraient dire que vous devriez le diviser en méthodes, mais la création de 40 méthodes non réutilisables ne semble pas correcte – j’ai donc décidé d’append simplement des accolades et des commentaires avant, donc cela ressemble à un bloc logique. Exemple:

 // Start video button {  } // Stop video button { < ...> } // Status label { < ...> } 

Je ne peux pas dire que ce soit la meilleure pratique, mais c’est une bonne pratique pour le code hérité.

Vous avez eu ces problèmes quand beaucoup de gens ont ajouté leurs propres composants à l’interface utilisateur et que certaines méthodes sont devenues très massives, mais il n’est pas pratique de créer 40 méthodes d’utilisation unique dans la classe qui ont déjà échoué.