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

今天为同事做代码审查时,我看到了一个奇特的事情。 他用这样的大括号包围了他的新代码:

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

如果有,结果如何? 这可能是什么原因呢? 这种习惯从哪里来?

编辑:

基于下面的input和一些问题,我觉得我必须补充一些问题,尽pipe我已经标记了一个答案。

环境是embedded式设备。 C ++服装中包含了很多遗留的C代码。 有很多C转向C ++开发人员。

在这部分代码中没有关键部分。 我只在代码的这一部分看到它。 没有大的内存分配完成,只是一些设置的标志,有些twiddling。

用花括号包围的代码就像这样:

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

(不要介意代码,只要坚持花括号…))大括号后,有一些更多的twiddling,状态检查,和基本信号。

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

从我的POV来看,这似乎相当奇怪,我不认为大括号应该在我们的代码中。 我在所有答案中看到了一些很好的例子,为什么用花括号包围代码,但是不应该将代码分解成方法吗?

这有时是很好的,因为它给了你一个新的范围,在那里你可以更“干净地”声明新的(自动)variables。

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

由于C++具有析构函数,因此在范围退出时可以自动释放资源(文件,互斥锁等),这样可以使事情变得更加简洁。 这意味着您可以保持一些共享资源的持续时间短于您在方法开始时抓取它的时间。

一个可能的目的是控制variables作用域 。 而且,由于具有自动存储的variables在超出范围时被销毁,这也可以使析构函数比其他函数更早被调用。

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

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

 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 ,同时可以使用额外的大括号来定义它的作用域。 这可以确保即使额外花括号外的代码可以被多个线程同时执行,花括号内的代码也会一次只执行一个线程

正如其他人所指出的那样,一个新的块引入了一个新的范围,使得人们可以用自己的variables来编写一些代码,这些variables不会丢弃周围代码的名称空间,也不会使用超过必要的资源。

不过,这样做还有另一个好的理由。

这只是隔离一个代码块,达到一个特定的(子)目的。 单个语句达到我想要的计算效果是很less见的; 通常需要几个。 把这些放在一个块(有评论),让我告诉读者(通常我自己在以后):

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

例如

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

你可能会说我应该写一个函数去做所有的事情。 如果我只做了一次,写一个函数只是增加了额外的语法和参数; 似乎没有什么意义。 只要把它看作一个无参数的匿名函数即可。

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

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

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

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

这个“显式范围”在以下情况下通常很有用:

  1. 避免名称冲突。
  2. using范围。
  3. 控制何时调用析构函数。

例1:

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

如果my_variable恰好是两个彼此独立使用的不同variables的特殊名称 ,那么显式范围允许您避免创build新名称,以避免名称冲突。

这也允许你避免意外地使用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. 

当需要释放资源不会自然地落在function或控制结构的边界上时,这对于RAII来说可能是重要的。

在multithreading编程的关键部分中使用作用域locking时,这非常有用。 在花括号(通常是第一个命令)中初始化的作用域锁将在块结束时超出作用域,所以其他线程将能够再次运行。

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

也许开发人员不相信这个编译器的寄存器分配,或者想通过一次限制范围内自动variables的数量来明确地控制堆栈帧的大小。

这里isInit可能会在堆栈上:

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

如果取出大括号, isInit空间可能会被保留在堆栈帧中,即使它可能被重用:如果有许多具有相似本地化范围的自动variables,并且堆栈大小有限,那可能是一个问题。

同样,如果你的variables被分配给一个寄存器,超出范围应该提供一个强有力的提示,即寄存器现在可以被重用。 你必须看看有没有大括号生成的汇编程序,以确定这是否真正的区别(和分析它 – 或看堆栈溢出 – 看看这种差异真的很重要)。

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

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

这种做法在debugging,embedded式设备或个人代码等特定情况下非常有用。

我同意“ruakh”。 如果你想在C中的各个级别的范围很好的解释,看看这个职位:

C应用程序中的各种级别的范围

一般情况下,如果您只想使用一个临时variables,而不必在函数调用的生命周期中跟踪,那么使用“块范围”是有帮助的。 另外,有些人使用它,所以为了方便起见,你可以在多个地方使用相同的variables名,虽然这通常不是一个好主意。 例如:

 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的临时实例。

我不能说通常这是一个好主意(也就是说,你可能不应该在块到块之间重复使用variables名),但总的来说,这样可以节省时间,并避免pipe理整个函数的returnValue值。

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

 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 

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

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

PS这不是坏的代码; 它是100%有效的 所以,这是一个(不寻常的)味道的问题。

在编辑过程中查看代码后,我可以说,不必要的括号可能(在原来的编码器视图中)100%清楚在if / then期间会发生什么,甚至现在只有一行,可能是更多的行后,括号保证你不会犯错误。

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

如果以上是原创的,删除“extras”会导致:

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

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

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

那当然会引起一个问题,因为现在isInit总是会被退回,不pipeif / then。

对象在超出范围时被自动销毁

用法的另一个例子是UI相关的类,特别是Qt。

例如,你有一些复杂的用户界面和很多的小部件,他们每个人都有自己的间距,布局等。而不是命名他们space1, space2, spaceBetween, layout1, ...你可以节省自己的非描述性名称只存在于二三行代码中的variables。

那么有人可能会说你应该把它拆分成方法,但是创build40个不可重用的方法看起来不太好 – 所以我决定在它们之前加上大括号和注释,所以看起来像是逻辑块。 例:

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

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

当很多人把自己的组件join到UI中,并且有些方法变得非常庞大的时候,他们遇到了这些问题,但是在内部创build了40个一次性使用方法是不现实的。