online indenter indentar identar codigo code beauty beautify c++ code-formatting

indenter - Llaves innecesarias en C++?



identar codigo c++ (14)

Al hacer una revisión del código para un colega hoy, vi algo peculiar. Había rodeado su nuevo código con llaves como esta:

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

¿Cuál es el resultado, si hay alguno, de esto? ¿Cuál podría ser el motivo para hacer esto? ¿De dónde viene este hábito?

Editar:

Con base en la información y en algunas preguntas a continuación, siento que debo agregar algo a la pregunta, aunque ya marqué una respuesta.

El medio ambiente es dispositivos integrados. Hay un montón de código C heredado envuelto en la ropa de C ++. Hay muchos desarrolladores de C convertidos en C ++.

No hay secciones críticas en esta parte del código. Solo lo he visto en esta parte del código. No se realizan grandes asignaciones de memoria, solo se configuran algunos indicadores y algunos se mueven un poco.

El código que está rodeado de llaves es algo así como:

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

(No se preocupe por el código, solo adhiérase a las llaves ...;)) Después de las llaves, hay algunos más movimientos, verificación de estado y señalización básica.

Hablé con el chico y su motivación fue limitar el alcance de las variables, nombrar enfrentamientos y alguna otra que realmente no pude entender.

Desde mi punto de vista esto parece bastante extraño y no creo que las llaves deberían estar en nuestro código. Vi algunos buenos ejemplos en todas las respuestas sobre por qué uno podría rodear el código con llaves, ¿pero no debería separar el código en métodos?


A veces es bueno, ya que le da un nuevo alcance, donde puede declarar "limpiamente" nuevas variables (automáticas).

En C++ esto quizás no sea tan importante ya que puedes introducir nuevas variables en cualquier lugar, pero tal vez el hábito sea de C , donde no podrías hacer esto hasta C99. :)

Como C++ tiene destructores, también puede ser útil tener recursos (archivos, mutexes, lo que sea) que se liberen automáticamente a medida que se cierre el alcance, lo que puede hacer las cosas más limpias. Esto significa que puede conservar algunos recursos compartidos durante un tiempo más corto de lo que lo haría si lo agarrara al comienzo del método.


Como han señalado otros, un nuevo bloque introduce un nuevo ámbito, que permite escribir un código con sus propias variables que no destruyen el espacio de nombres del código circundante y no utiliza recursos más de lo necesario.

Sin embargo, hay otra buena razón para hacer esto.

Es simplemente para aislar un bloque de código que logra un (sub) propósito particular. Es raro que una sola declaración logre un efecto computacional que quiero; usualmente toma varios. Colocarlos en un bloque (con un comentario) me permite decirle al lector (a menudo yo mismo en una fecha posterior):

  • Este pedazo tiene un propósito conceptual coherente
  • Aquí está todo el código necesario
  • Y aquí hay un comentario sobre el pedazo.

p.ej

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

Podría argumentar que debería escribir una función para hacer todo eso. Si solo lo hago una vez, escribir una función solo agrega sintaxis y parámetros adicionales; parece poco sentido. Solo piense en esto como una función anónima y sin parámetros.

Si tiene suerte, su editor tendrá una función de plegado / desplegado que incluso le permitirá ocultar el bloque.

Hago esto todo el tiempo. Es un gran placer conocer los límites del código que necesito inspeccionar, y aún mejor saber que si ese pedazo no es el que quiero, no tengo que mirar ninguna de las líneas.


Creo que otros ya han cubierto el alcance, así que mencionaré que las llaves innecesarias también podrían servir para el propósito en el proceso de desarrollo. Por ejemplo, supongamos que está trabajando en una optimización para una función existente. Alternar la optimización o rastrear un error a una secuencia particular de declaraciones es simple para el programador; vea el comentario antes de las llaves:

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

Esta práctica es útil en ciertos contextos, como la depuración, los dispositivos integrados o el código personal.


Después de ver el código en la edición, puedo decir que los corchetes innecesarios son probablemente (en la vista de los codificadores originales) 100% claros de lo que sucederá durante el si / then, incluso si ahora solo hay una línea, podría ser más líneas más adelante, y los corchetes garantizan que no cometerá un error.

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

si lo anterior era original, y eliminar "extras" resultaría en:

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

luego, una modificación posterior podría verse así:

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

y eso, por supuesto, causaría un problema, ya que ahora isInit siempre sería devuelto, independientemente de si / then.


Entonces, ¿por qué usar llaves "innecesarias"?

  • Para fines de "Scoping" (como se mencionó anteriormente)
  • Hacer el código más legible de una manera (más o menos como usar #pragma , o definir "secciones" que se pueden visualizar)
  • Porque tú puedes. Simple como eso.

PD: no es un código MALO; es 100% valido Entonces, es más bien una cuestión de gusto (poco común).


Esto es lo mismo que un bloque if (o while etc.), simplemente sin if . En otras palabras, introduce un alcance sin introducir una estructura de control.

Este "alcance explícito" suele ser útil en los siguientes casos:

  1. Para evitar conflictos de nombre.
  2. Para using alcance
  3. Para controlar cuándo se llaman los destructores.

Ejemplo 1:

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

Si my_variable resulta ser un nombre particularmente bueno para dos variables diferentes que se utilizan de forma aislada, el alcance explícito le permite evitar inventar un nuevo nombre solo para evitar el choque de nombre.

Esto también le permite evitar my_variable uso de my_variable fuera del alcance previsto.

Ejemplo 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. // ... } }

Las situaciones prácticas cuando esto es útil son raras y pueden indicar que el código está listo para la refactorización, pero el mecanismo está ahí en caso de que realmente lo necesite.

Ejemplo 3:

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

Esto puede ser importante para RAII en los casos en que la necesidad de liberar recursos no "cae" naturalmente en los límites de las funciones o estructuras de control.


Esto es realmente útil cuando se utilizan bloqueos de ámbito junto con secciones críticas en la programación multiproceso. El bloqueo del objetivo inicializado en las llaves (generalmente el primer comando) saldrá del alcance al final del bloque y para que otros hilos puedan volver a ejecutarse.


Estoy de acuerdo con "ruakh". Si desea una buena explicación de los distintos niveles de alcance en C, consulte esta publicación:

Varios niveles de alcance en la aplicación C

En general, el uso del "Alcance del bloque" es útil si solo desea usar una variable temporal de la que no tenga que realizar un seguimiento durante toda la vida de la llamada a la función. Además, algunas personas lo usan para que pueda usar el mismo nombre de variable en varias ubicaciones por conveniencia, aunque generalmente no es una buena idea. p.ej:

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; }

En este ejemplo particular, he definido returnValue dos veces, pero dado que está solo en el alcance del bloque, en lugar del alcance de la función (es decir, el alcance de la función sería, por ejemplo, declarar returnValue justo después de int main (void)), no Obtiene cualquier error del compilador, ya que cada bloque es ajeno a la instancia temporal de returnValue declaró.

No puedo decir que esta sea una buena idea en general (es decir, probablemente no deba volver a usar nombres de variables repetidamente de bloque a bloque), pero en general, ahorra tiempo y le permite evitar tener que administrar el valor de returnValue en toda la función.

Finalmente, tenga en cuenta el alcance de las variables utilizadas en mi muestra de código:

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


Las llaves adicionales se utilizan para definir el alcance de la variable declarada dentro de las llaves. Se hace para llamar al destructor cuando la variable se sale del alcance. En el destructor, puede liberar un mutex (o cualquier otro recurso) para que otro pueda adquirirlo.

En mi código de producción, he escrito algo como esto:

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 }

Como puede ver, de esta manera, puede usar scoped_lock en una función y, al mismo tiempo, puede definir su alcance utilizando llaves adicionales. Esto garantiza que, aunque el código que está fuera de los refuerzos adicionales se pueda ejecutar por varios subprocesos simultáneamente, el código dentro de los refuerzos se ejecutará exactamente con un subproceso a la vez.


Los objetos se destruyen automágicamente cuando salen del alcance ...


Otro ejemplo de uso son las clases relacionadas con la interfaz de usuario, especialmente Qt.

Por ejemplo, tiene una interfaz de usuario complicada y muchos widgets, cada uno de ellos tiene su propio espacio, diseño, etc. En lugar de denominarlos space1, space2, spaceBetween, layout1, ... puede guardarse de nombres no descriptivos para las variables que existen solo en dos o tres líneas de código.

Bueno, algunos podrían decir que debes dividirlo en métodos, pero crear 40 métodos no reutilizables no se ve bien, así que decidí simplemente agregar llaves y comentarios antes que ellos, por lo que parece un bloque lógico. Ejemplo:

// Start video button { <here the code goes> } // Stop video button { <...> } // Status label { <...> }

No puedo decir que sea la mejor práctica, pero es buena para el código heredado.

Tengo estos problemas cuando muchas personas agregaron sus propios componentes a la interfaz de usuario y algunos métodos se volvieron realmente masivos, pero no es práctico crear 40 métodos de una sola vez dentro de la clase que ya se han estropeado.


Todos los demás ya cubrieron correctamente las posibilidades de scoping, RAII, etc., pero como mencionas un entorno incrustado, hay una razón potencial adicional:

Quizás el desarrollador no confíe en la asignación de registros de este compilador o quiera controlar explícitamente el tamaño del marco de la pila al limitar el número de variables automáticas en el alcance a la vez.

Aquí isInit probablemente estará en la pila:

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

Si quita las llaves, se puede reservar espacio para isInit en el marco de la pila incluso después de que pueda ser reutilizado: si hay muchas variables automáticas con alcance similarmente localizado, y el tamaño de la pila es limitado, eso podría ser un problema .

De forma similar, si su variable está asignada a un registro, salirse del alcance debería proporcionar una pista fuerte de que el registro está ahora disponible para su reutilización. Tendría que mirar el ensamblador generado con y sin llaves para descubrir si esto marca una diferencia real (y perfilarlo - o ver si hay desbordamiento de pila - para ver si esta diferencia realmente importa).


Un posible propósito es controlar el alcance de la variable . Y dado que las variables con almacenamiento automático se destruyen cuando salen del alcance, esto también puede permitir que un destructor sea llamado antes de lo que lo haría.


Una razón podría ser que el tiempo de vida de las variables declaradas dentro del nuevo bloque de llaves se restringe a este bloque. Otra razón que me viene a la mente es poder usar el plegado de código en el editor favorito.