Accolades inutiles en C ++?


187

En faisant une révision de code pour un collègue aujourd'hui, j'ai vu une chose particulière. Il avait entouré son nouveau code avec des 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 en être la raison? D'où vient cette habitude?

Éditer:

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

L'environnement est constitué de périphériques embarqués. Il y a beaucoup de code C hérité enveloppé dans des vêtements C ++. Il y a beaucoup de développeurs C devenus C ++.

Il n'y a pas de sections critiques dans cette partie du code. Je ne l'ai vu que dans cette partie du code. Il n'y a pas d'allocations de mémoire majeures, juste quelques indicateurs qui sont définis et quelques petits changements.

Le code entouré d'accolades est quelque chose comme:

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

(Ne vous inquiétez pas du code, tenez-vous-en aux accolades ...;)) Après les accolades, il y a un peu plus de twiddling, de vérification d'état et de signalisation de base.

J'ai parlé au gars et sa motivation était de limiter la portée des variables, de nommer les conflits et d'autres que je ne pouvais pas vraiment saisir.

De mon point de vue, cela semble plutôt étrange et je ne pense pas que les accolades devraient être dans notre code. J'ai vu de bons exemples dans toutes les réponses sur les raisons pour lesquelles on pouvait entourer le code avec des accolades, mais ne devriez-vous pas plutôt séparer le code en méthodes?


99
Quelle a été la réponse de votre collègue lorsque vous lui avez demandé pourquoi il l'avait fait?
Graham Borland

20
Assez commun avec le modèle RAII. Aperçu rapide: c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
Marcin

9
Je déteste les bretelles frisées inutiles
jacknad

8
Y avait-il des déclarations dans le bloc interne?
Keith Thompson

15
peut-être qu'il voulait simplement `` plier '' facilement cette nouvelle section dans son éditeur
wim

Réponses:


281

C'est parfois agréable car cela vous donne une nouvelle portée, où vous pouvez déclarer plus "proprement" de nouvelles variables (automatiques).

Dans C++ce n'est peut-être pas si important puisque vous pouvez introduire de nouvelles variables n'importe où, mais peut-être que l'habitude vient C, où vous ne pourriez pas faire cela avant C99. :)

Depuis qu'il C++a des destructeurs, il peut également être pratique de libérer automatiquement des ressources (fichiers, mutex, etc.) à la sortie de la portée, ce qui peut rendre les choses plus propres. Cela signifie que vous pouvez conserver une ressource partagée pendant une durée plus courte que si vous l'aviez récupérée au début de la méthode.


37
+1 pour la mention explicite de nouvelles variables et de vieilles habitudes
arne

46
+1 pour l'utilisation de la portée de bloc utilisée pour libérer des ressources aussi rapidement que possible
Leo

9
Il est également facile de «si (0)» un bloc.
vrdhn

Mes réviseurs de code me disent souvent que j'écris des fonctions / méthodes trop courtes. Pour les garder heureux et me garder heureux (c'est-à-dire pour séparer les problèmes sans rapport, les localités variables, etc.), j'utilise juste cette technique élaborée par @unwind.
ossandcad

21
@ossandcad, ils vous disent que vos méthodes sont "trop ​​courtes"? C'est extrêmement difficile à faire. 90% des développeurs (moi-même probablement inclus) ont le problème inverse.
Ben Lee

169

Un objectif possible est de contrôler la portée des variables . Et comme les variables avec stockage automatique sont détruites lorsqu'elles sont hors de portée, cela peut également permettre à un destructeur d'être appelé plus tôt qu'il ne le ferait autrement.


14
Bien sûr, ce bloc devrait vraiment être transformé en une fonction distincte.
BlueRaja - Danny Pflughoeft

8
Note historique: Il s'agit d'une technique du premier langage C qui permettait la création de variables temporaires locales.
Thomas Matthews

12
Je dois dire - bien que je sois satisfait de ma réponse, ce n'est vraiment pas la meilleure réponse ici; les meilleures réponses mentionnent explicitement RAII, car c'est la principale raison pour laquelle vous voudriez qu'un destructeur soit appelé à un moment donné. Cela ressemble à un cas de "l'arme la plus rapide de l'Ouest": j'ai posté assez vite pour avoir suffisamment de votes positifs tôt pour que je gagne "l'élan" pour gagner des votes plus rapidement que de meilleures réponses. Non pas que je me plains! :-)
ruakh

7
@ BlueRaja-DannyPflughoeft Vous simplifiez à l'extrême. «Mettez-le dans une fonction séparée» n'est pas la solution à tous les problèmes de code. Le code de l'un de ces blocs peut être étroitement couplé au code environnant, touchant plusieurs de ses variables. Utilisation des fonctions C, qui nécessite des opérations de pointeur. De plus, tous les extraits de code ne sont pas (ou ne devraient pas être) réutilisables, et parfois le code peut même ne pas avoir de sens en soi. Je mets parfois des blocs autour de mes fordéclarations pour créer un int i;C89 de courte durée . Vous ne suggérez sûrement pas que chacun fordevrait être dans une fonction distincte?
Anders Sjöqvist

101

Les accolades supplémentaires sont utilisées pour définir la portée de la variable déclarée à l'intérieur des accolades. Cela est fait pour que le destructeur soit appelé lorsque la variable sort de la portée. Dans le destructeur, vous pouvez libérer un mutex (ou toute autre ressource) afin que d'autres puissent 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 manière, vous pouvez utiliser scoped_lock dans une fonction et en même temps définir sa portée 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.


1
Je pense que c'est plus simple d'avoir: scoped_lock lock (mutex) // code de section critique puis lock.unlock ().
sizzle

17
@szielenski: Et si le code de la section critique lève une exception? Soit le mutex sera verrouillé pour toujours, soit le code ne sera pas aussi propre que vous l'avez dit.
Nawaz

4
@Nawaz: L'approche de @ szielenski ne laissera pas le mutex verrouillé en cas d'exceptions. Il utilise également un scoped_lockqui sera détruit sur les exceptions. Je préfère généralement introduire une nouvelle portée pour le verrou, mais dans certains cas, unlockc'est très utile. Par exemple, pour déclarer une nouvelle variable locale dans la section critique et l'utiliser plus tard. (Je sais que je suis en retard, mais juste pour être complet ...)
Stephan

51

Comme d'autres l'ont souligné, un nouveau bloc introduit une nouvelle portée, permettant d'écrire un peu de code avec ses propres variables qui ne détruisent pas l'espace de noms du code environnant et n'utilisent pas les ressources plus longtemps que nécessaire.

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

Il s'agit simplement d'isoler un bloc de code qui atteint un (sous) objectif particulier. Il est rare qu'une seule instruction produise un effet de calcul que je souhaite; il en faut généralement plusieurs. Les placer dans un bloc (avec un commentaire) me permet de dire au lecteur (souvent moi-même à une date ultérieure):

  • Ce morceau a un objectif 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 dire que je devrais écrire une fonction pour faire tout cela. Si je ne le fais qu'une seule fois, l'écriture d'une fonction ajoute simplement une syntaxe et des paramètres supplémentaires; il semble peu utile. Pensez simplement à cela comme une fonction anonyme et sans paramètre.

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

Je le fais tout le temps. C'est un grand plaisir de connaître les limites du code que je dois inspecter, et encore mieux de savoir que si ce morceau n'est pas celui que je veux, je n'ai à regarder aucune des lignes.


23

Une raison pourrait être que la durée de vie de toutes les variables déclarées à l'intérieur du nouveau bloc d'accolades est limitée à ce bloc. Une autre raison qui me vient à l'esprit est de pouvoir utiliser le pliage de code dans l'éditeur favori.


17

C'est la même chose qu'un bloc if(ou whileetc.), juste sans if . En d'autres termes, vous introduisez une étendue sans introduire de structure de contrôle.

Cette "portée explicite" est généralement utile dans les cas suivants:

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

Exemple 1:

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

// ...

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

S'il my_variables'avère que c'est un nom particulièrement bon pour deux variables différentes qui sont utilisées isolément l'une de l'autre, alors la portée explicite vous permet d'éviter d'inventer un nouveau nom juste pour éviter le conflit de nom.

Cela vous permet également d'éviter d'utiliser my_variablehors de sa portée 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 où cela est utile sont rares et peuvent indiquer que le code est mûr pour la refactorisation, mais le mécanisme est là si vous en avez vraiment 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ù la nécessité de libérer des ressources ne «tombe» pas naturellement sur les limites des fonctions ou des structures de contrôle.


15

Ceci est vraiment utile lorsque vous utilisez des verrous étendus en conjonction avec des sections critiques dans la programmation multithread. Votre verrou de portée initialisé dans les accolades (généralement la première commande) sortira de la portée à la fin de la fin du bloc et ainsi les autres threads pourront s'exécuter à nouveau.


14

Tous les autres ont déjà couvert correctement les possibilités de portée, RAII, etc., mais comme vous mentionnez un environnement embarqué, il y a une autre raison potentielle:

Peut-être que le développeur ne fait pas confiance à l'allocation de registre de ce compilateur ou souhaite contrôler explicitement la taille de la trame de la pile en limitant le nombre de variables automatiques dans la portée à la fois.

Voici isInitprobablement sur la pile:

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

Si vous supprimez les accolades, l'espace pour isInitpeut être réservé dans le cadre de la pile même après qu'il pourrait potentiellement être réutilisé: s'il y a beaucoup de variables automatiques avec une portée localisée similaire et que la taille de votre pile est limitée, cela pourrait être un problème.

De même, si votre variable est allouée à un registre, sortir de la portée devrait fournir un indice fort que le registre est maintenant disponible pour une réutilisation. Vous devriez regarder l'assembleur généré avec et sans les accolades pour déterminer si cela fait une vraie différence (et le profiler - ou surveiller le débordement de pile - pour voir si cette différence compte vraiment).


+1 bon point, même si je suis assez sûr que les compilateurs modernes réussissent sans intervention. (IIRC - pour les compilateurs non intégrés au moins - ils ont ignoré le mot clé 'register' aussi loin que '99 car ils pouvaient toujours faire un meilleur travail que vous ne le pourriez.)
Rup

11

Je pense que d'autres ont déjà couvert la portée, je vais donc mentionner que les accolades inutiles pourraient également servir dans le processus de développement. Par exemple, supposons que vous travaillez sur une optimisation d'une fonction existante. Basculer l'optimisation ou tracer un bogue vers une séquence particulière d'instructions est simple pour le programmeur - voir le commentaire avant 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.


10

Je suis d'accord avec "ruakh". Si vous voulez une bonne explication des différents niveaux de portée en C, consultez cet article:

Différents niveaux de portée dans l'application C

En général, l'utilisation de "Block scope" est utile si vous voulez simplement utiliser une variable temporaire dont vous n'avez pas besoin de garder une trace pendant la durée de vie de l'appel de fonction. De plus, certaines personnes l'utilisent pour que vous puissiez utiliser le même nom de variable à plusieurs endroits pour plus de commodité, bien que ce ne soit 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 s'agit simplement de la portée du bloc, au lieu de la portée de la fonction (c'est-à-dire: la portée de la fonction serait, par exemple, de déclarer returnValue juste après int main (void)), je ne le fais pas obtenir toutes les erreurs du compilateur, car chaque bloc est inconscient de l'instance temporaire de returnValue déclarée.

Je ne peux pas dire que c'est une bonne idée en général (c'est-à-dire: vous ne devriez probablement pas réutiliser les noms de variables à plusieurs reprises d'un bloc à l'autre), mais en général, cela fait gagner du temps et vous évite d'avoir à gérer le valeur de returnValue dans toute la fonction.

Enfin, veuillez noter la portée 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

Question occupée, mec. Je n'ai jamais eu 100 ups. Quelle est la particularité de cette question? Bon lien. C est plus précieux que C ++.
Wolfpack'08

5

Alors, pourquoi utiliser des accolades "inutiles"?

  • À des fins de «cadrage» (comme mentionné ci-dessus)
  • Rendre le code plus lisible d'une manière (un peu comme utiliser #pragmaou définir des «sections» qui peuvent être visualisées)
  • Parce que vous pouvez. Aussi simple que cela.

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


5

Après avoir vu le code dans l'édition, je peux dire que les crochets inutiles sont probablement (dans la vue des codeurs d'origine) pour être 100% clair ce qui se passera pendant le si / alors, même s'il ne s'agit que d'une ligne maintenant, cela pourrait être 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 la suppression des "extras" entraînera:

{
   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 retourné, quel que soit le if / then.


4

Les objets sont automatiquement détruits lorsqu'ils sont hors de portée ...


2

Les classes liées à l'interface utilisateur, en particulier Qt, sont un autre exemple d'utilisation.

Par exemple, vous avez une interface utilisateur et beaucoup de widgets, chacun d'entre eux a obtenu son propre espace, mise en page et etc. Au lieu de les nommer compliquées , space1, space2, spaceBetween, layout1, ...vous pouvez vous épargner des noms non-descriptives pour les variables qui existent seulement dans deux-trois lignes de code.

Eh bien, certains pourraient dire que vous devriez le diviser en méthodes, mais créer 40 méthodes non réutilisables ne semble pas correct - j'ai donc décidé de simplement ajouter des accolades et des commentaires avant eux, donc cela ressemble à un bloc logique. Exemple:

// Start video button 
{ 
   <here the code goes> 
}
// 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é.

J'ai eu ces problèmes lorsque beaucoup de gens ont ajouté leurs propres composants à l'interface utilisateur et que certaines méthodes sont devenues vraiment massives, mais il n'est pas pratique de créer 40 méthodes d'utilisation unique à l'intérieur d'une classe qui a déjà gâché.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.