今天为同事做代码审查时,我看到了一件奇怪的事情。他用这样的大括号包围了他的新代码:
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;
}
}
(不要介意代码,只要坚持花括号......;)) 在花括号之后还有一些比特,状态检查和基本信号。
我和那个人交谈过,他的动机是限制变量的范围,命名冲突以及其他一些我无法真正接受的冲突。
从我的POV看起来这看起来很奇怪,我不认为花括号应该在我们的代码中。我在所有答案中都看到了一些很好的例子,为什么人们可以用花括号括起代码,但是你不应该把代码分成方法吗?
答案 0 :(得分:263)
它有时很好,因为它为您提供了一个新的范围,您可以更“干净地”声明新的(自动)变量。
在C++
中,这可能不是那么重要,因为你可以在任何地方引入新变量,但习惯可能来自C
,在C99之前你不能这样做。 :)
由于C++
具有析构函数,因此在作用域退出时自动释放资源(文件,互斥体等)也很方便,这可以使事情更清晰。这意味着您可以保留一些共享资源的持续时间比在方法开始时抓取它的时间短。
答案 1 :(得分:166)
一个可能的目的是control variable scope。并且由于具有自动存储的变量在超出范围时会被销毁,这也可以使析构函数更早地被调用。
答案 2 :(得分:98)
额外的大括号用于定义在大括号内声明的变量的范围。这样做是为了在变量超出范围时调用析构函数。在析构函数中,您可以释放互斥锁(或任何其他资源),以便其他人可以获取它。
在我的生产代码中,我写了类似的东西:
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
,同时可以使用额外的大括号来定义其范围。这可以确保即使额外大括号外的代码可以同时由多个线程执行,大括号内的代码也会一次由完全一个线程执行。
答案 3 :(得分:49)
正如其他人所指出的那样,一个新的块引入了一个新的范围,使得人们可以用自己的变量编写一些代码,这些变量不会破坏周围代码的命名空间,并且不会使用超过必要的。
然而,这是另一个很好的理由。
它只是隔离一个实现特定(子)目的的代码块。单个语句很少能达到我想要的计算效果;通常它需要几个。将这些放在一个区块(带注释)允许我告诉读者(通常我自己以后):
e.g。
{ // update the moving average
i= (i+1) mod ARRAYSIZE;
sum = sum - A[i];
A[i] = new_value;
sum = sum + new_value;
average = sum / ARRAYSIZE ;
}
你可能会说我应该写一个函数来做所有这些。如果我只做一次,编写一个函数只会添加额外的语法和参数;似乎没什么意义。只需将其视为无参数的匿名函数。
如果你很幸运,你的编辑器将有一个折叠/展开功能,甚至可以让你隐藏该块。
我一直这样做。很高兴知道我需要检查的代码的界限,甚至更好地知道如果那个块不是我想要的那个,我不必看任何一行。
答案 4 :(得分:23)
一个原因可能是在新花括号块内声明的任何变量的生命周期都限制在此块中。想到的另一个原因是能够在喜欢的编辑器中使用代码折叠。
答案 5 :(得分:16)
这与if
(或while
etc ..)块相同,只是没有 if
。换句话说,在不引入控制结构的情况下引入范围。
这种“显式范围”通常适用于以下情况:
using
。示例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,当释放资源的需求不会“落入”功能或控制结构的边界时,这可能很重要。
答案 6 :(得分:14)
在多线程编程中将scoped锁与关键部分结合使用时,这非常有用。在花括号中初始化的作用域锁(通常是第一个命令)将在块结束时超出作用域,因此其他线程将能够再次运行。
答案 7 :(得分:12)
其他人已经正确地涵盖了范围,RAII等可能性,但是既然你提到了嵌入式环境,还有一个潜在的原因:
也许开发人员不信任这个编译器的寄存器分配,或者想通过一次限制范围内自动变量的数量来显式控制堆栈帧大小。
此处isInit
可能会出现在堆栈中:
{
bool isInit;
(void)isStillInInitMode(&isInit);
if (isInit) {
return isInit;
}
}
如果取出花括号,isInit
的空间可能会在堆栈框架中保留,即使它可能被重用:如果有很多具有类似本地化范围的自动变量,并且您的堆栈大小是有限,这可能是一个问题。
同样,如果您的变量被分配给一个寄存器,那么超出范围应该提供一个强烈的提示,即寄存器现在可以重复使用。您必须查看使用和不使用大括号生成的汇编程序,以确定这是否会产生真正的差异(并对其进行分析 - 或者观察堆栈溢出 - 以查看这种差异是否真的重要)。
答案 8 :(得分:11)
我认为其他人已经涵盖范围界定,所以我提到不必要的括号也可能在开发过程中起作用。例如,假设您正在对现有函数进行优化。对于程序员来说,切换优化或将错误跟踪到特定的语句序列是很简单的 - 请参阅大括号之前的注释:
// if (false) or if (0)
{
//experimental optimization
}
这种做法在某些情况下非常有用,例如调试,嵌入式设备或个人代码。
答案 9 :(得分:10)
我同意“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),I不要得到任何编译器错误,因为每个块都不知道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
答案 10 :(得分:5)
那么,为什么要使用“不必要的”花括号?
#pragma
,或定义可视化的“部分”)P.S。这不是BAD代码;这是100%有效。所以,这是(不寻常)品味的问题。
答案 11 :(得分: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 / then如何都会返回isInit。
答案 12 :(得分:4)
对象在超出范围时会自动销毁...
答案 13 :(得分:1)
另一个使用示例是与UI相关的类,尤其是Qt。
例如,你有一些复杂的UI和很多小部件,每个小部件都有自己的间距,布局等。而不是命名它们space1, space2, spaceBetween, layout1, ...
你可以保存自己的变量的非描述性名称仅存在于两三行代码中。
嗯,有些人可能会说你应该在方法中拆分它,但创建40个不可重用的方法看起来不行 - 所以我决定在它们之前添加大括号和注释,所以它看起来像逻辑块。 例如:
// Start video button
{
<here the code goes>
}
// Stop video button
{
<...>
}
// Status label
{
<...>
}
不能说这是最好的做法,但对于遗留代码来说这是一个很好的做法。
当许多人将自己的组件添加到UI并且某些方法变得非常庞大时遇到了这些问题,但是在已经搞砸的类中创建40个一次性使用方法是不切实际的。