【问题标题】:Unnecessary curly braces in C++?C ++中不必要的花括号?
【发布时间】:2012-03-31 00:53:51
【问题描述】:

今天在为同事进行代码审查时,我看到了一件奇怪的事情。他用这样的花括号括住了他的新代码:

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}

如果有的话,结果是什么?这样做的原因可能是什么?这种习惯从何而来?

编辑:

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

环境是嵌入式设备。有很多遗留的 C 代码包裹在 C++ 服装中。有很多 C 转为 C++ 的开发人员。

这部分代码没有关键部分。我只在这部分代码中看到过。没有完成主要的内存分配,只是设置了一些标志,并进行了一些操作。

花括号括起来的代码是这样的:

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

(不要介意代码,只要坚持花括号...;)) 在花括号之后还有一些更多的小玩意、状态检查和基本信号。

我和那个人谈过,他的动机是限制变量的范围、命名冲突以及其他一些我无法真正理解的东西。

从我的观点来看,这似乎很奇怪,我认为花括号不应该出现在我们的代码中。我在所有答案中都看到了一些很好的例子,说明为什么可以用花括号括起代码,但你不应该将代码分成方法吗?

【问题讨论】:

  • 当你问他为什么这样做时,你的同事的回答是什么?
  • 在 RAII 模式中很常见。快速概览:c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
  • 我讨厌不必要的花括号
  • 内部块中是否有任何声明?
  • 也许他只是想在编辑器中轻松地“折叠”掉那个新部分

标签: c++ code-formatting


【解决方案1】:

一个可能的目的是control variable scope。而且由于具有自动存储功能的变量在超出范围时会被销毁,这也可以使析构函数能够比其他情况更早地被调用。

【讨论】:

  • 当然,真的应该把那个块做成一个单独的函数。
  • 历史记录:这是一种来自早期 C 语言的技术,允许创建局部临时变量。
  • 我不得不说——虽然我对我的答案很满意,但这真的不是最好的答案;更好的答案明确提到了 RAII,因为它是 why 您希望在特定点调用析构函数的主要原因。这似乎是“西方最快的枪”的案例:我发布的速度足够快,以至于我获得了足够的早期支持,以至于我获得了“动力”,从而比一些更好的答案更快地获得支持。不是我在抱怨! :-)
  • @BlueRaja-DannyPflughoeft 你过于简单化了。 “把它放在一个单独的函数中”并不是每个代码问题的解决方案。这些块之一中的代码可能与周围的代码紧密耦合,涉及其中的几个变量。使用 C 函数,这需要指针操作。此外,并非每个代码 sn-p 都是(或应该)可重用的,有时代码本身甚至可能没有意义。我有时会在我的 for 语句周围放置块,以在 C89 中创建一个短暂的 int i;。您肯定不是建议每个for 都应该在一个单独的函数中?
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    C++ 中,这可能不是那么重要,因为您可以在任何地方引入新变量,但也许这种习惯来自C,直到C99 才可以这样做。 :)

    由于C++ 具有析构函数,因此在范围退出时自动释放资源(文件、互斥体等)也很方便,这可以使事情变得更干净。这意味着您可以在更短的时间内保留一些共享资源,而不是在方法开始时抓住它。

    【讨论】:

    • +1 用于明确提及新变量和旧习惯
    • +1 使用块作用域尽可能快地释放资源
    • 'if (0)' 块也很容易。
    • @ossandcad,他们告诉你你的方法“太短了”?这是极难做到的。 90% 的开发人员(可能包括我自己)都有相反的问题。
    • @displayName 不同的是,您可以拥有非自动的“新变量”,因此您需要手动为它们分配内存。 (例如,使用“new”关键字)
    【解决方案4】:

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

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

    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
    }
    

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

    【讨论】:

    • 我认为它更简洁:scoped_lock lock(mutex) //critical section code then lock.unlock().
    • @szielenski:如果临界区的代码抛出异常怎么办?要么互斥锁将永远被锁定,要么代码不会像你说的那样更干净
    • @Nawaz:@szielenski 的方法不会在出现异常时锁定互斥锁。他还使用了scoped_lock,它将在异常时被破坏。我通常也喜欢为锁引入一个新的作用域,但在某些情况下,unlock 非常有用。例如。在临界区中声明一个新的局部变量,然后再使用它。 (我知道我迟到了,但只是为了完整……)
    【解决方案5】:

    我同意“ruakh”。如果您想很好地解释 C 中不同级别的范围,请查看这篇文章:

    Various Levels of Scope in C Application

    一般来说,如果您只想使用一个在函数调用的生命周期内不必跟踪的临时变量,则使用“块范围”会很有帮助。此外,有些人使用它,因此您可以在多个位置使用相同的变量名以方便起见,尽管这通常不是一个好主意。例如:

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

    在这个特定的例子中,我定义了 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
    

    【讨论】:

    • 忙问题,伙计。我从来没有上过100个。这个问题有什么特别之处?很好的链接。 C 比 C++ 更有价值。
    【解决方案6】:

    对象超出范围时会自动销毁...

    【讨论】:

      【解决方案7】:

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

      但是,这样做还有另一个很好的理由。

      它只是隔离实现特定(子)目的的代码块。很少有单个语句达到我想要的计算效果;通常需要几个。将它们放在一个块中(带有评论)可以让我告诉读者(通常是我自己在以后):

      • 这个块有一个连贯的概念目的
      • 这里有所有需要的代码
      • 这里是关于块的评论。

      例如

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

      你可能会争辩说我应该写一个函数来完成这一切。如果我只做一次,写一个函数只会增加额外的语法和参数;似乎没什么意义。把它想象成一个无参数的匿名函数。

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

      我一直这样做。很高兴知道我需要检查的代码的范围,甚至更好地知道如果该块不是我想要的,我不必查看任何行。

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

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

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

          这种做法在调试、嵌入式设备或个人代码等特定环境中很有用。

          【讨论】:

            【解决方案10】:

            这与if(或while 等)块相同,只是没有 if。换句话说,您引入了范围而不引入控制结构。

            这种“显式作用域”通常在以下情况下很有用:

            1. 为了避免名称冲突。
            2. 作用域using
            3. 控制何时调用析构函数。

            示例 1:

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

            如果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.
                    // ...
                }
            
            }
            

            这种有用的实际情况很少见,可能表明代码已经成熟,可以重构,但如果你真的需要它,这种机制就在那里。

            示例 3:

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

            这对于RAII 来说很重要,因为释放资源的需求不会自然地“落入”功能或控制结构的边界。

            【讨论】:

              【解决方案11】:

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

              • 用于“范围界定”目的(如上所述)
              • 以某种方式使代码更具可读性(很像使用#pragma,或定义可以可视化的“部分”)
              • 因为你可以。就那么简单。

              附:这不是坏代码;它是 100% 有效的。所以,这是一个(不常见的)品味问题。

              【讨论】:

                【解决方案12】:

                在编辑中查看代码后,我可以说不必要的括号可能(在原始编码人员视图中)100% 清楚在 if/then 期间会发生什么,即使现在只有一行,后面可能会多行,括号保证你不会出错。

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

                如果以上内容是原创的,并且删除“附加”将导致:

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

                那么,以后的修改可能如下所示:

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

                这当然会引起问题,因为无论 if/then 是什么,现在总是会返回 isInit。

                【讨论】:

                  【解决方案13】:

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

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

                  这里isInit 可能会在堆栈中:

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

                  如果你去掉花括号,isInit 的空间可能会保留在堆栈框架中,即使它可能被重用:如果有许多具有类似本地化范围的自动变量,并且你的堆栈大小是有限的,这可能是个问题。

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

                  【讨论】:

                  • +1 好点,虽然我相当肯定现代编译器在没有干预的情况下做到了这一点。 (IIRC - 至少对于非嵌入式编译器 - 他们早在 99 年就忽略了 'register' 关键字,因为他们总是可以做得比你做得更好。)
                  【解决方案14】:

                  另一个使用示例是与 UI 相关的类,尤其是 Qt。

                  例如,您有一些复杂的 UI 和许多小部件,每个小部件都有自己的间距、布局等。与其将它们命名为 space1, space2, spaceBetween, layout1, ...,您可以将自己从只存在的变量的非描述性名称中解脱出来在两三行代码中。

                  好吧,有些人可能会说你应该将它拆分为方法,但是创建 40 个不可重用的方法看起来不太好 - 所以我决定在它们之前添加大括号和 cmets,所以它看起来像逻辑块。 示例:

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

                  不能说这是最佳做法,但对于遗留代码来说这是一个很好的做法。

                  当很多人将自己的组件添加到 UI 并且某些方法变得非常庞大时,就会遇到这些问题,但是在已经搞砸的类中创建 40 个一次性使用方法是不切实际的。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2018-05-02
                    • 2019-07-11
                    • 1970-01-01
                    • 2012-09-12
                    • 2019-01-26
                    • 2014-02-16
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多