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


186

今天在为一位同事进行代码审查时,我看到了一件奇怪的事情。他用大括号将新代码括起来,如下所示:

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

(不用管代码,只需要紧握大括号...;)在大括号之后,还有更多的旋转,状态检查和基本的信号传递。

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

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


98
当您问他为什么这么做时,您同事的回答是什么?
Graham Borland

20
RAII模式非常常见。快速概览:c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
Marcin

9
我讨厌不必要的花括号
jacknad 2012年

8
内部块中是否有任何声明?
基思·汤普森

15
也许他只是想轻松地“折叠”他的编辑器中的新部分
2012年

Answers:


281

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

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

由于C++具有析构函数,因此在范围退出时自动释放资源(文件,互斥对象等)也很方便,这可以使事情变得更整洁。这意味着您可以比在方法开始时获取共享资源的时间短。


37
+1明确提及新变量和旧习惯
Arne

46
+1用于使用块范围来尽可能快地释放资源
Leo

9
'if(0)'一个块也很容易。
vrdhn 2012年

我的代码审阅者经常告诉我,我编写的函数/方法太短了。为了让他们开心并让自己开心(即,分离无关的问题,可变的位置等),我仅使用@unwind阐述的这种技术。
ossandcad 2012年

21
@ossandcad,他们告诉您您的方法“太短”了吗?那很难做到。90%的开发人员(可能包括我自己)遇到了相反的问题。
李·李

169

一种可能的目的是控制可变范围。并且由于具有自动存储的变量在超出范围时会被破坏,因此这也可以使析构函数的调用早于其他方式。


14
当然,实际上应该将该块做成一个单独的函数。
BlueRaja-Danny Pflughoeft 2012年

8
历史注释:这是一种来自早期C语言的技术,允许创建局部临时变量。
Thomas Matthews 2012年

12
我不得不说-尽管我对我的回答感到满意,但这实际上并不是最好的答案。更好的答案提RAII明确,因为它是主要的原因,为什么你会想要一个析构函数在特定点被调用。这似乎是“西方最快的枪支”的例子:我发布得足够快,以至于我获得了足够的早期投票,获得了“动力”,从而获得了比一些更好的答案更快的投票。并不是说我在抱怨!:-)
ruakh 2012年

7
@ BlueRaja-DannyPflughoeft您太简单了。“把它放在一个单独的函数中”并不是解决每个代码问题的方法。这些块之一中的代码可能与周围的代码紧密耦合,涉及到其中的几个变量。使用C函数,需要指针操作。此外,并非每个代码段都是(或应该)可重用的,有时代码本身可能甚至没有意义。有时,我会在for语句周围加一些块,以int i;在C89中创建一个短暂的语句。当然,您不是在建议不要将每个for函数都放在单独的函数中吗?
2014年

101

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

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

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 在一个函数中使用它,同时可以通过使用额外的花括号来定义其范围。这样可以确保即使多余括号外的代码可以同时由多个线程执行,括号内的代码一次也只能由一个线程执行。


1
我认为这样比较干净:scoped_lock lock(mutex)//关键部分代码,然后是lock.unlock()。
2012年

17
@szielenski:如果关键部分中的代码引发异常怎么办?要么互斥锁将永远被锁定,要么代码不会像您所说的那样干净
纳瓦兹

4
@Nawaz:@szielenski的方法不会在发生异常的情况下锁定互斥锁。他还使用scoped_lock会在异常情况下被破坏的。我通常也喜欢为锁引入新的作用域,但是在某些情况下,unlock它非常有用。例如,在关键部分声明一个新的局部变量,然后在以后使用它。(我知道我来晚了,但只是为了完整性...)
Stephan

51

正如其他人指出的那样,新块引入了新的作用域,使一个人可以用自己的变量编写一些代码,这些变量不会浪费周围代码的名称空间,并且不会使用超出必要时间的资源。

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

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

  • 该块具有一致的概念目的
  • 这是所有需要的代码
  • 这是关于块的评论。

例如

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

您可能会争辩说我应该编写一个函数来完成所有这些工作。如果我只做一次,编写函数只会添加其他语法和参数。似乎没有什么意义。只需将此视为无参数的匿名函数即可。

如果幸运的话,您的编辑器将具有折叠/展开功能,甚至可以让您隐藏该块。

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


23

一个原因可能是在新花括号块内声明的任何变量的生存期都限于该块。想到的另一个原因是能够在收藏夹编辑器中使用代码折叠。


17

这与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可能很重要。


15

当在多线程编程中将范围锁与关键部分结合使用时,这非常有用。在花括号中初始化的范围锁(通常是第一个命令)将在块末尾超出范围,因此其他线程将能够再次运行。


14

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

也许开发人员不信任该编译器的寄存器分配,或者想通过一次限制作用域中自动变量的数量来显式地控制堆栈帧的大小。

这里isInit可能在堆栈中:

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

如果取出花括号,则isInit即使可能重新使用了花括号,也可能在堆栈框架中保留空间:如果有很多自动变量的作用域类似,并且堆栈大小有限,则可能是个问题。

同样,如果将变量分配给寄存器,则超出范围应提供一个强有力的暗示,表明该寄存器现在可供重用。您必须查看使用大括号和不使用大括号生成的汇编器,以查明这是否真正有区别(并对其进行概要分析-或注意堆栈溢出-看看这种区别是否真正重要)。


+1好一点,尽管我相当确定现代编译器在没有干预的情况下就能正确实现这一点。(IIRC-至少对于非嵌入式编译器-他们早在'99时就忽略了'register'关键字,因为它们总是可以做得比您做得更好。)
Rup 2012年

11

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

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

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


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

在此特定示例中,我两次定义了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 ++更有价值。
Wolfpack'08 2012年

5

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

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

PS这不是错误代码;100%有效。因此,这是(不常见)味道的问题。


5

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

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

如果上面的内容是原始的,那么删除“ extras” woudl会导致:

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

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

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

这当然会引起问题,因为现在无论是否有if,那么始终会返回isInit。


4

当对象超出范围时,它们会被自动破坏。


2

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

例如,您有一些复杂的UI和许多小部件,每个小部件都有自己的间距,布局等。您无需命名它们,而无需为space1, space2, spaceBetween, layout1, ...仅存在于两行三行中的变量使用非描述性名称。码。

好吧,有人可能会说您应该将其拆分为方法,但是创建40个不可重用的方法似乎并不可行-因此我决定只在它们之前添加括号和注释,因此看起来像是逻辑块。例:

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

不能说这是最佳实践,但是对于遗留代码而言,这是一个好习惯。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.