C++中不必要的花括号?

iik*_*koo 175 c++ code-formatting

今天为同事做代码审查时,我看到了一件奇怪的事情.他用这样的花括号包围了他的新代码:

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}
Run Code Online (Sandbox Code Playgroud)

如果有的话,结果是什么?这可能是什么原因?这种习惯来自哪里?

编辑:

根据输入和下面的一些问题,我觉得我必须在问题上添加一些,即使我已经标记了答案.

环境是嵌入式设备.C++服装中包含许多遗留的C代码.有很多C转向C++开发人员.

这部分代码中没有关键部分.我只在代码的这一部分中看到过它.没有完成主要的内存分配,只是设置了一些标志,而且有点笨拙.

大括号包围的代码类似于:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}
Run Code Online (Sandbox Code Playgroud)

(不要介意代码,只需坚持花括号......;))花括号之后还有一些比特麻烦,状态检查和基本信号.

我与那个人谈过,他的动机是限制变量的范围,命名冲突,以及其他一些我无法真正接受的冲突.

从我的POV看起来相当奇怪,我不认为花括号应该在我们的代码中.我在所有答案中都看到了一些很好的例子,说明为什么用花括号包围代码,但是你不应该把代码分成方法吗?

unw*_*ind 271

它有时很好,因为它为您提供了一个新的范围,您可以更"干净地"声明新的(自动)变量.

C++这也许不那么重要,因为你可以在任何地方引入新的变量,但也许是习惯是C,你不能这样做,直到C99.:)

由于C++具有析构函数,因此在作用域退出时自动释放资源(文件,互斥体等)也很方便,这可以使事情更清晰.这意味着您可以保持一些共享资源的持续时间比在方法开始时抓取它的时间短.

  • +1用于使用块范围用于尽可能快地释放资源 (43认同)
  • +1用于明确提及新变量和旧习惯 (35认同)
  • @ossandcad,他们告诉你你的方法"太短"了吗?这很难做到.90%的开发人员(可能包括我自己)都有相反的问题. (20认同)
  • "if(0)"块也很容易. (9认同)

rua*_*akh 167

一个可能的目的是控制变量范围.并且由于具有自动存储的变量在超出范围时会被销毁,这也可以使析构函数更早地被调用.

  • 当然,真正的那个块应该只是一个单独的功能. (14认同)
  • 我不得不说 - 虽然我对我的答案感到满意,但这里的答案真的不是最好的答案.更好的答案明确提到RAII,因为它是*为什么*你想要在特定点调用析构函数的主要原因.这似乎是"西方最快的枪"的情况:我发布得足够快,我得到了足够的早期投票,我获得了"动力",比一些更好的答案获得更快的投票.不是我在抱怨!:-) (12认同)
  • 历史记录:这是一种来自早期C语言的技术,它允许创建本地临时变量. (8认同)
  • @ BlueRaja-DannyPflughoeft你过于简单了."将它放在一个单独的函数中"并不是每个代码问题的解决方案.其中一个块中的代码可能与周围的代码紧密耦合,触及其中的几个变量.使用C函数,需要指针操作.此外,并非每个代码片段都是(或应该)可重用的,有时代码本身甚至没有意义.我有时会在`for`语句周围放置块来在C89中创建一个短命的`int i;`.当然你并不是说每个'for`应该是一个单独的功能? (7认同)

Naw*_*waz 100

额外的大括号用于定义在大括号内声明的变量的范围.这样做是为了在变量超出范围时调用析构函数.在析构函数中,您可以释放互斥锁(或任何其他资源),以便其他人可以获取它.

在我的生产代码中,我写了这样的东西:

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
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,通过这种方式,您可以scoped_lock 在函数中使用,同时可以使用额外的大括号来定义其范围.这确保了即使额外大括号外的代码可以同时由多个线程执行,大括号内的代码也会一次只由一个线程执行.

  • @szielenski:如果来自关键部分的代码抛出异常怎么办?互斥锁将永远被锁定,或者代码不会像你说的那样*更干净*. (17认同)
  • @Nawaz:@ szielenski的方法不会在异常的情况下锁定互斥锁.他还使用了一个将在例外情况下被破坏的`scoped_lock`.我通常也喜欢为锁引入新的范围,但在某些情况下,`unlock`非常有用.例如,在临界区内声明一个新的局部变量,然后再使用它.(我知道我迟到了,但仅仅是为了完整......) (3认同)

Ira*_*ter 50

正如其他人所指出的那样,一个新的块引入了一个新的范围,使得人们可以用自己的变量编写一些代码,这些变量不会破坏周围代码的命名空间,并且不会使用超过必要的资源.

然而,这是另一个很好的理由.

它只是隔离一个实现特定(子)目的的代码块.单个语句很少能达到我想要的计算效果; 通常它需要几个.将这些放在一个区块(带注释)允许我告诉读者(通常我自己以后):

  • 这个块具有连贯的概念目的
  • 这是所需的所有代码
  • 这是关于大块的评论.

例如

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

你可能会说我应该写一个函数来完成所有这些.如果我只做一次,编写一个函数只会添加额外的语法和参数; 似乎没什么意义.只需将其视为无参数,匿名函数即可.

如果你很幸运,你的编辑器将有一个折叠/展开功能,甚至可以让你隐藏块.

我一直这样做.很高兴知道我需要检查的代码的边界,甚至更好地知道如果那个块不是我想要的那个,我不必看任何行.


arn*_*rne 23

一个原因可能是在新花括号块内声明的任何变量的生命周期都限制在此块中.想到的另一个原因是能够在喜欢的编辑器中使用代码折叠.


Bra*_*vic 16

这与if(或while等)块相同,只是没有 if.换句话说,在不引入控制结构的情况下引入范围.

这种"显式范围"通常适用于以下情况:

  1. 避免名字冲突.
  2. 范围using.
  3. 控制析构函数的调用时间.

例1:

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

// ...

{
    auto my_variable = ... ;
    // ...
}
Run Code Online (Sandbox Code Playgroud)

如果my_variable恰好是彼此隔离使用的两个不同变量的特别好的名称,那么显式作用域允许您避免发明新名称以避免名称冲突.

这也可以避免my_variable意外使用超出预期范围.

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

}
Run Code Online (Sandbox Code Playgroud)

有用的实际情况很少见,可能表明代码已经适合重构,但机制是你应该真正需要它.

例3:

{
    MyRaiiClass guard1 = ...;

    // ...

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

    // ...

} // ~MyRaiiClass for guard1 called.
Run Code Online (Sandbox Code Playgroud)

当对于释放资源的需求自然地"落入"功能或控制结构的边界时,这对于RAII来说可能是重要的.


lea*_*vst 14

在多线程编程中结合使用范围锁定时,这非常有用.在花括号中初始化的作用域锁(通常是第一个命令)将在块结束时超出作用域,因此其他线程将能够再次运行.


Use*_*ess 12

其他人已经正确地涵盖了范围,RAII等可能性,但是既然你提到了嵌入式环境,还有一个潜在的原因:

也许开发人员不信任这个编译器的寄存器分配,或者希望通过一次限制范围内自动变量的数量来显式控制堆栈帧大小.

这里isInit可能会在堆栈上:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}
Run Code Online (Sandbox Code Playgroud)

如果你取出花括号,isInit即使它可能被重用,也可以在堆栈框架中保留空间:如果有很多具有类似本地化范围的自动变量,并且你的堆栈大小有限,那可能是个问题.

同样,如果您的变量被分配给一个寄存器,那么超出范围应该提供一个强烈的提示,即寄存器现在可以重复使用.您必须查看使用和不使用大括号生成的汇编程序,以确定这是否会产生真正的差异(并对其进行分析 - 或者观察堆栈溢出 - 以查看这种差异是否真正重要).


小智 11

我认为其他人已经涵盖范围界定,所以我会提到不必要的括号也可能在开发过程中起作用.例如,假设您正在对现有函数进行优化.对于程序员来说,切换优化或将错误跟踪到特定的语句序列是很简单的 - 请参阅大括号之前的注释:

// if (false) or if (0) 
{
   //experimental optimization  
}
Run Code Online (Sandbox Code Playgroud)

在某些情况下,例如调试,嵌入式设备或个人代码,这种做法很有用.


Clo*_*oud 10

我同意"ruakh".如果你想要很好地解释C中不同级别的范围,请查看这篇文章:

C应用中的各种范围

通常,如果您只想使用一个临时变量来跟踪函数调用的生命周期,那么使用"块作用域"会很有帮助.此外,有些人使用它,因此您可以在多个位置使用相同的变量名称以方便,但这通常不是一个好主意.例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

在这个特定的例子中,我已经定义了returnValue两次,但由于它只是在块作用域,而不是函数作用域(即:函数作用域,例如,在int main(void)之后声明returnValue),我不得到任何编译器错误,因为每个块都不知道returnValue声明的临时实例.

我不能说这通常是一个好主意(即:你可能不应该从块到块重复使用变量名称),但一般来说,它可以节省时间并让你避免必须管理整个函数的returnValue值.

最后,请注意我的代码示例中使用的变量的范围:

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
Run Code Online (Sandbox Code Playgroud)


Dr.*_*eon 5

那么,为什么要使用"不必要的"花括号呢?

  • 用于"范围"目的(如上所述)
  • 使代码在某种程度上更具可读性(非常类似于使用#pragma或定义可视化的"部分")
  • 因为你能.就那么简单.

PS这不是BAD代码; 这是100%有效.所以,这是(不常见的)品味的问题.


nyc*_*nik 5

在查看编辑中的代码之后,我可以说不必要的括号可能(在原始编码器视图中)100%清除在if/then期间会发生什么,即使它现在只有一行,它可能是以后会有更多行,并且括号可以保证您不会出错.

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
   return -1;
}
Run Code Online (Sandbox Code Playgroud)

如果以上是原始的,并删除"额外"woudl导致:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     return isInit;
   return -1;
}
Run Code Online (Sandbox Code Playgroud)

然后,稍后的修改可能如下所示:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     CallSomethingNewHere();
     return isInit;
   return -1;
}
Run Code Online (Sandbox Code Playgroud)

当然,这会引起一个问题,因为现在无论if/then如何都会返回isInit.