Al hacer una revisión de código para un colega hoy, vi una cosa 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 lo hay, de esto? ¿Cuál podría ser la razón para hacer esto? ¿De dónde viene este hábito?
Editar:
En base a los comentarios y algunas preguntas a continuación, siento que tengo que agregar algunas a la pregunta, a pesar de que ya marqué una respuesta.
El entorno es dispositivos integrados. Hay una gran cantidad de código C heredado envuelto en ropa C ++. Hay muchos desarrolladores 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 asignaciones importantes de memoria, solo algunos indicadores que se configuran y algunos cambios de bits.
El código que está rodeado de llaves se parece a:
{
bool isInit;
(void)isStillInInitMode(&isInit);
if (isInit) {
return isInit;
}
}
(No importa el código, solo quédese con las llaves ...;)) Después de las llaves hay un poco más de torsión, 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 otros que realmente no pude entender.
Desde mi punto de vista, esto parece bastante extraño y no creo que las llaves estén 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?
fuente
Respuestas:
A veces es bueno, ya que te da un nuevo alcance, donde puedes declarar más "limpiamente" nuevas variables (automáticas).
En
C++
esto no es quizá tan importante ya que se puede introducir nuevas variables en cualquier lugar, pero tal vez la costumbre es deC
, donde no se podía hacer esto hasta C99. :)Dado que
C++
tiene destructores, también puede ser útil tener recursos (archivos, mutexes, lo que sea) liberados automáticamente a medida que sale el alcance, lo que puede hacer las cosas más limpias. Esto significa que puede conservar algún recurso compartido durante un período más corto de lo que lo haría si lo tomara al comienzo del método.fuente
Un posible propósito es controlar el alcance variable . Y dado que las variables con almacenamiento automático se destruyen cuando salen del alcance, esto también puede permitir que se llame a un destructor antes de lo que lo haría de otra manera.
fuente
for
declaraciones para crear unint i;
C89 de corta duración . ¿Seguramente no estás sugiriendo que todosfor
deberían estar en una función separada?Las llaves adicionales se utilizan para definir el alcance de la variable declarada dentro de las llaves. Se hace para que se llame al destructor cuando la variable se salga del alcance. En el destructor, puede liberar un mutex (o cualquier otro recurso) para que otros puedan adquirirlo.
En mi código de producción, he escrito algo como esto:
Como puede ver, de esta manera, puede usar
scoped_lock
una función y, al mismo tiempo, puede definir su alcance mediante el uso de llaves adicionales. Esto garantiza que, aunque el código que se encuentra fuera de los corchetes adicionales pueda ejecutarse mediante múltiples subprocesos simultáneamente, el código dentro de los corchetes se ejecutará exactamente por un subproceso a la vez.fuente
scoped_lock
que será destruido en las excepciones. Por lo general, prefiero introducir un nuevo alcance para el bloqueo también, pero en algunos casosunlock
es muy útil. Por ejemplo, para declarar una nueva variable local dentro de la sección crítica y luego usarla más tarde. (Sé que llego tarde, pero para completar ...)Como otros han señalado, un nuevo bloque introduce un nuevo alcance, lo que le permite a uno escribir un poco de código con sus propias variables que no destruyen el espacio de nombres del código circundante, y no usa recursos más de lo necesario.
Sin embargo, hay otra buena razón para hacer esto.
Es simplemente 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; Por lo general, se necesitan varios. Colocarlos en un bloque (con un comentario) me permite decirle al lector (a menudo a mí mismo en una fecha posterior):
p.ej
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 que no tiene mucho sentido. Solo piense en esto como una función anónima 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 fragmento no es el que quiero, no tengo que mirar ninguna de las líneas.
fuente
Una razón podría ser que la vida útil de cualquier variable declarada dentro del nuevo bloque de llaves se restringe a este bloque. Otra razón que viene a la mente es poder usar el plegado de código en el editor favorito.
fuente
Esto es lo mismo que un bloque
if
(owhile
etc.), solo que sinif
. En otras palabras, introduce un ámbito sin introducir una estructura de control.Este "alcance explícito" suele ser útil en los siguientes casos:
using
.Ejemplo 1:
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 nombres.Esto también le permite evitar usar
my_variable
fuera de su alcance previsto por accidente.Ejemplo 2
Las situaciones prácticas en las que esto es útil son raras y pueden indicar que el código está maduro para la refactorización, pero el mecanismo está ahí si alguna vez lo necesita realmente.
Ejemplo 3:
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 las estructuras de control.
fuente
Esto es realmente útil cuando se usan bloqueos de ámbito junto con secciones críticas en la programación multiproceso. Su bloqueo de ámbito inicializado en las llaves (generalmente el primer comando) quedará fuera de alcance al final del bloque y, por lo tanto, otros subprocesos podrán ejecutarse nuevamente.
fuente
Todos los demás ya cubrieron correctamente las posibilidades de alcance, RAII, etc., pero como mencionas un entorno incrustado, hay otra razón potencial:
Tal vez el desarrollador no confía en la asignación de registros de este compilador o quiere controlar explícitamente el tamaño del marco de la pila limitando el número de variables automáticas en el alcance a la vez.
Aquí
isInit
probablemente estará en la pila:Si saca las llaves,
isInit
se puede reservar espacio para el marco de la pila incluso después de que podría reutilizarse: si hay muchas variables automáticas con un alcance similarmente localizado, y el tamaño de su pila es limitado, eso podría ser un problema.Del mismo modo, si su variable está asignada a un registro, salir del alcance debería proporcionar una pista clara de que el registro ahora está disponible para su reutilización. Tendría que mirar el ensamblador generado con y sin las llaves para determinar si esto hace una diferencia real (y perfilarlo, o observar el desbordamiento de la pila, para ver si esta diferencia realmente importa).
fuente
Creo que otros ya han cubierto el alcance, por lo que mencionaré que las llaves innecesarias también pueden servir para el proceso de desarrollo. Por ejemplo, suponga que está trabajando en una optimización para una función existente. Cambiar 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:
Esta práctica es útil en ciertos contextos como la depuración, los dispositivos integrados o el código personal.
fuente
Estoy de acuerdo con "ruakh". Si desea una buena explicación de los diversos niveles de alcance en C, consulte esta publicación:
Varios niveles de alcance en la aplicación C
En general, el uso de "Alcance de bloque" es útil si solo desea usar una variable temporal de la que no tiene que realizar un seguimiento durante la vida útil 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:
En este ejemplo en 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 obtener cualquier error del compilador, ya que cada bloque es ajeno a la instancia temporal de returnValue declarada.
No puedo decir que esta sea una buena idea en general (es decir: probablemente no debería reutilizar nombres de variables repetidamente de bloque a bloque), pero en general, ahorra tiempo y le permite evitar tener que administrar valor de returnValue en toda la función.
Finalmente, tenga en cuenta el alcance de las variables utilizadas en mi ejemplo de código:
fuente
Entonces, ¿por qué usar llaves "innecesarias"?
#pragma
o definir "secciones" que se pueden visualizar)PD: no es un código MALO; Es 100% válido. Entonces, es más bien una cuestión de gusto (poco común).
fuente
Después de ver el código en la edición, puedo decir que los corchetes innecesarios probablemente (en la vista de los codificadores originales) estén 100% claros de lo que sucederá durante el si / entonces, aunque ahora solo sea una línea, podría ser más líneas después, y los corchetes le garantizan que no cometerá un error.
si lo anterior era original, y eliminar "extras" daría como resultado:
entonces, una modificación posterior podría verse así:
y eso, por supuesto, causaría un problema, ya que ahora isInit siempre se devolvería, independientemente de si / entonces.
fuente
Los objetos se destruyen automáticamente cuando salen del alcance ...
fuente
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 nombrarlos
space1, space2, spaceBetween, layout1, ...
, puede salvarse de nombres no descriptivos para variables que existen solo en dos o tres líneas de código.Bueno, algunos podrían decir que debería dividirlo en métodos, pero crear 40 métodos no reutilizables no se ve bien, así que decidí agregar llaves y comentarios antes de ellos, por lo que parece un bloque lógico. Ejemplo:
No puedo decir que sea la mejor práctica, pero es buena para el código heredado.
Obtuve 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 uso único dentro de la clase que ya estaban en mal estado.
fuente