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++
具有析构函数,因此在作用域退出时自动释放资源(文件,互斥体等)也很方便,这可以使事情更清晰.这意味着您可以保持一些共享资源的持续时间比在方法开始时抓取它的时间短.
rua*_*akh 167
一个可能的目的是控制变量范围.并且由于具有自动存储的变量在超出范围时会被销毁,这也可以使析构函数更早地被调用.
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
在函数中使用,同时可以使用额外的大括号来定义其范围.这确保了即使额外大括号外的代码可以同时由多个线程执行,大括号内的代码也会一次只由一个线程执行.
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)
你可能会说我应该写一个函数来完成所有这些.如果我只做一次,编写一个函数只会添加额外的语法和参数; 似乎没什么意义.只需将其视为无参数,匿名函数即可.
如果你很幸运,你的编辑器将有一个折叠/展开功能,甚至可以让你隐藏块.
我一直这样做.很高兴知道我需要检查的代码的边界,甚至更好地知道如果那个块不是我想要的那个,我不必看任何行.
Bra*_*vic 16
这与if
(或while
等)块相同,只是没有 if
.换句话说,在不引入控制结构的情况下引入范围.
这种"显式范围"通常适用于以下情况:
using
.例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来说可能是重要的.
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中不同级别的范围,请查看这篇文章:
通常,如果您只想使用一个临时变量来跟踪函数调用的生命周期,那么使用"块作用域"会很有帮助.此外,有些人使用它,因此您可以在多个位置使用相同的变量名称以方便,但这通常不是一个好主意.例如:
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)
那么,为什么要使用"不必要的"花括号呢?
#pragma
或定义可视化的"部分")PS这不是BAD代码; 这是100%有效.所以,这是(不常见的)品味的问题.
在查看编辑中的代码之后,我可以说不必要的括号可能(在原始编码器视图中)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.