C++ 在单行 if 或 loop 中使用大括号(即 {})的目的是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12193170/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:59:48  来源:igfitidea点击:

What's the purpose of using braces (i.e. {}) for a single-line if or loop?

c++ccoding-stylecurly-bracesdefensive-programming

提问by JAN

I'm reading some lecture notes of my C++ lecturer and he wrote the following:

我正在阅读我的 C++ 讲师的一些讲义,他写了以下内容:

  1. Use Indentation // OK
  2. Never rely on operator precedence - Always use parentheses // OK
  3. Always use a { } block - even for a single line // not OK, why ???
  4. Const object on left side of comparison // OK
  5. Use unsigned for variables that are >= 0 // nice trick
  6. Set Pointer to NULL after deletion - Double delete protection // not bad
  1. 使用缩进 // OK
  2. 从不依赖运算符优先级 - 始终使用括号 // OK
  3. 始终使用 { } 块 - 即使是单行 //也不行,为什么???
  4. 比较左侧的 const 对象 // OK
  5. 对 >= 0 的变量使用 unsigned // 很好的技巧
  6. 删除后将指针设置为 NULL - 双重删除保护 // 不错

The 3rd technique is not clear to me: what would I gain by placing one line in a { ... }?

我不清楚第三种技术:在 a 中放置一行会得到什么{ ... }

For example, take this weird code:

例如,拿这个奇怪的代码:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

and replace it with:

并将其替换为:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;

What's the benefit of using the 1st version?

使用第一个版本有什么好处?

回答by Luchian Grigore

Let's attempt to also modify iwhen we increment j:

让我们尝试i在增加时也进行修改j

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;

Oh no! Coming from Python, this looks ok, but in fact it isn't, as it's equivalent to:

不好了!来自 Python,这看起来不错,但实际上并非如此,因为它相当于:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

Of course, this is a silly mistake, but one that even an experienced programmer could make.

当然,这是一个愚蠢的错误,但即使是有经验的程序员也会犯。

Another very good reasonis pointed out in ta.speot.is's answer.

ta.speot.is 的回答中指出了另一个很好的理由

A thirdone I can think of is nested if's:

我能想到的第三个是嵌套if的:

if (cond1)
   if (cond2) 
      doSomething();

Now, assume you now want to doSomethingElse()when cond1is not met (new feature). So:

现在,假设您现在想要doSomethingElse()什么时候cond1不满足(新功能)。所以:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

which is obviously wrong, since the elseassociates with the inner if.

这显然是错误的,因为else与内部关联if



Edit: Since this is getting some attention, I'll clarify my view. The question I was answering is:

编辑:由于这引起了一些关注,我将澄清我的观点。我回答的问题是:

What's the benefit of using the 1st version?

使用第一个版本有什么好处?

Which I have described. There are some benefits. But, IMO, "always" rules don't always apply. So I don't wholly support

我已经描述过。有一些好处。但是,IMO,“始终”规则并不总是适用。所以我不完全支持

Always use a { } block - even for a single line // not OK, why ???

始终使用 { } 块 - 即使是单行 // 也不行,为什么???

I'm not saying alwaysuse a {}block. If it's a simple enough condition & behavior, don't. If you suspect someone might come in later & change your code to add functionality, do.

我不是说总是使用{}块。如果这是一个足够简单的条件和行为,请不要。如果您怀疑有人稍后会进来并更改您的代码以添加功能,请这样做。

回答by ta.speot.is

It's very easy to accidentally change control-flow with comments if you do not use {and }. For example:

如果不使用{and ,很容易意外更改带有注释的控制流}。例如:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();

If you comment out do_something_else()with a single line comment, you'll end up with this:

如果你do_something_else()用单行注释注释掉,你会得到这样的结果:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();

It compiles, but must_always_do_this()isn't always called.

它编译,但must_always_do_this()并不总是被调用。

We had this issue in our code base, where someone had gone in to disable some functionality very quickly before release. Fortunately we caught it in code review.

我们的代码库中存在这个问题,有人在发布之前很快就禁用了某些功能。幸运的是,我们在代码中发现了它。

回答by James Kanze

I have my doubts as to the competence of the lecturer. Considering his points:

我对讲师的能力表示怀疑。考虑到他的观点:

  1. OK
  2. Would anyone really write (or want to read) (b*b) - ((4*a)*c)? Some precedences are obvious (or should be), and the extra parentheses just add to confusion. (On the other hand, you _should_ use the parentheses in less obvious cases, even if you know that they're not needed.)
  3. Sort of. There are two wide spread conventions for formatting conditionals and loops:
    if ( cond ) {
        code;
    }
    
    and:
    if ( cond )
    {
        code;
    }
    
    In the first, I'd agree with him. The opening {is not that visible, so it's best to assume it's always there. In the second, however, I (and most of the people I've worked with) have no problem with omitting the braces for a single statement. (Provided, of course, that the indentation is systematic and that you use this style consistently. (And a lot of very good programmers, writing very readable code, omit the braces even when formatting the first way.)
  4. NO. Things like if ( NULL == ptr )are ugly enough to hinder readability. Write the comparisons intuitively. (Which in many cases results in the constant on the right.) His 4 is bad advice; anything which makes the code unnatural makes it less readable.
  5. NO. Anything but intis reserved for special cases. To experienced C and C++ programmers, the use of unsignedsignals bit operators. C++ doesn't have a real cardinal type (or any other effective subrange type); unsigneddoesn't work for numeric values, because of the promotion rules. Numerical values on which no arithmetic operations would make sense, like serial numbers, could presumably be unsigned. I'd argue against it, however, because it sends the wrong message: bitwise operations don't make sense either. The basic rule is that integral types are int, _unless_ there is a significant reason for using another type.
  6. NO. Doing this systematically is misleading, and doesn't actually protect against anything. In strict OO code, delete this;is often the most frequent case (and you can't set thisto NULL), and otherwise, most deleteare in destructors, so you can't access the pointer later anyway. And setting it to NULLdoesn't do anything about any other pointers floating around. Setting the pointer systematically to NULLgives a false sense of security, and doesn't really buy you anything.
  1. 好的
  2. 真的有人会写(或想读)(b*b) - ((4*a)*c)吗?一些优先级是显而易见的(或应该是),而额外的括号只会增加混乱。(另一方面,您_应该_在不太明显的情况下使用括号,即使您知道不需要它们。)
  3. 有点。格式化条件和循环有两种广泛传播的约定:
    if ( cond ) {
        code;
    }
    
    和:
    if ( cond )
    {
        code;
    }
    
    首先,我同意他的看法。开口{不是那么明显,所以最好假设它总是在那里。然而,在第二种情况下,我(以及与我共事过的大多数人)对于在单个语句中省略大括号没有问题。(当然,前提是缩进是系统的,并且您始终如一地使用这种风格。(而且很多非常优秀的程序员,编写了非常易读的代码,即使在格式化第一种方式时也省略了大括号。)
  4. 。诸如此类if ( NULL == ptr )的东西丑陋到足以阻碍可读性。直观地写出比较。(这在很多情况下会导致右边的常数。)他的 4 是坏建议;任何使代码不自然的东西都会使其可读性降低。
  5. 。除了int为特殊情况保留的任何内容。对于有经验的 C 和 C++ 程序员,unsigned信号位运算符的使用。C++ 没有真正的基数类型(或任何其他有效的子范围类型);unsigned由于促销规则,不适用于数值。没有算术运算有意义的数值,如序列号,大概是unsigned. 然而,我会反对它,因为它传递了错误的信息:按位运算也没有意义。基本规则是整数类型是int,除非有重要的原因使用另一种类型。
  6. 。系统地这样做是一种误导,实际上并不能防止任何事情。在严格的 OO 代码中,delete this;通常是最常见的情况(并且您不能设置thisNULL),否则,大多数delete都在析构函数中,因此无论如何您以后都无法访问指针。并将其设置为NULL对任何其他浮动的指针没有任何作用。系统地将指针设置为NULL给人一种虚假的安全感,并不会真正为您购买任何东西。

Look at the code in any of the typical references. Stroustrup violates every rule you've given except for the first, for example.

查看任何典型参考中的代码。例如,Stroustrup 违反了你给出的每一条规则,除了第一条规则。

I'd suggest that you find another lecturer. One who actually knows what he's talking about.

我建议你再找一个讲师。一个真正知道他在说什么的人。

回答by Konrad Rudolph

All the other answers defend your lecturer's rule 3.

所有其他答案都为您的讲师规则 3 辩护。

Let me say that I agree with you: the rule is redundantand I wouldn't advise it. It's true that it theoreticallyprevents errors if you always add curly brackets. On the other hand, I've never encountered this problem in real life: contrary to what other answers imply, I've not once forgotten to add the curly brackets once they became necessary. If you use proper indentation, it becomes immediately obvious that you need to add curly brackets once more than one statement is indented.

让我说我同意你的看法:这条规则是多余的,我不建议这样做。确实,如果您总是添加大括号,它理论上可以防止错误。另一方面,我在现实生活中从未遇到过这个问题:与其他答案所暗示的相反,我从来没有忘记在大括号变得必要时添加它们。如果您使用适当的缩进,一旦缩进多个语句,您立即需要添加大括号就变得很明显了。

The answer by “Component 10” actually highlights the only conceivable case where this could really lead to an error. But on the other hand, replacing code via regular expression always warrants enormous care anyway.

“组件 10”的答案实际上强调了唯一可能会真正导致错误的情况。但另一方面,无论如何,通过正则表达式替换代码总是需要格外小心。

Now let's look at the other side of the medal: is there a disadvantageto always using curly brackets? The other answers simply ignore this point. But there isa disadvantage: it takes up a lot of vertical screen space, and this in turn can make your code unreadable because it means you have to scroll more than necessary.

现在让我们看看奖牌的另一面:总是使用大括号有什么缺点吗?其他答案只是忽略了这一点。但是,一个缺点:它占用了大量的垂直屏幕空间,而这反过来又可以让你的代码不可读,因为这意味着你必须滚动超过必要的。

Consider a function with a lot of guard clauses at the beginning (and yes, the following is bad C++ code but in other languages this would be quite a common situation):

考虑一个开头有很多保护子句的函数(是的,以下是糟糕的 C++ 代码,但在其他语言中,这将是很常见的情况):

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Cannot do method on identical objects");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Precondition for a not met");
    }

    a->do_something_with(b);
}

This is horrible code, and I argue strongly that the following is vastly more readable:

这是一段糟糕的代码,我强烈认为以下内容更具可读性:

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Cannot do method on identical objects");
    if (not a->precondition_met())
        throw logic_error("Precondition for a not met");

    a->do_something_with(b);
}

Similarly, short nested loops benefit from omitting the curly brackets:

同样,短嵌套循环受益于省略大括号:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}

Compare with:

与之比较:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}

The first code is concise; the second code is bloated.

第一段代码简洁;第二个代码是臃肿的。

And yes, this can be mitigated to some extentby putting the opening brace on the previous line. But that would stillbe less readable than the code without any curly brackets.

是的,这可以在一定程度上通过将左括号放在上一行来缓解。但这仍然比没有任何大括号的代码可读性差。

In short: don't write unnecessary code which takes up screen space.

简而言之:不要编写占用屏幕空间的不必要代码。

回答by jam

The codebase I'm working on is scattered with code by people with a pathological aversion to braces, and for the people who come along later, it really can make a difference to maintainability.

我正在处理的代码库中散布着由病态地厌恶大括号的人编写的代码,对于后来出现的人来说,它确实可以对可维护性产生影响。

The most frequent problematic example I have encountered is this:

我遇到的最常见的问题示例是:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
    this_looks_like_a_then-statement_but_isn't;

So when I come along and wish to add a then-statement, I can easily end up with this if I'm not careful:

所以当我想添加一个 then 语句时,如果我不小心,我很容易得到这个:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then-statement_but_it's_not;
}

Given that it takes ~1second to add braces and can save you at minimum a few confused minutes debugging, why would you ever notgo with the reduced-ambiguity option? Seems like false economy to me.

鉴于添加大括号需要大约 1 秒的时间,并且可以为您节省至少几分钟的调试时间,为什么您使用减少歧义的选项?对我来说似乎是虚假的经济。

回答by Nemanja Trifunovic

My 2c:

我的 2c:

Use Indentation

使用缩进

Obviously

明显地

Never rely on operator precedence - Always use parentheses

从不依赖运算符优先级 - 始终使用括号

I wouldn't use words "never and "always", but in general I see this rule being useful. In some languages (Lisp, Smalltalk) this is a non-issue.

我不会使用“从不”和“总是”这样的词,但总的来说,我认为这条规则很有用。在某些语言(Lisp、Smalltalk)中,这不是问题。

Always use a { } block - even for a single line

始终使用 { } 块 - 即使是单行

I never do that and never had a single problem, but I can see how it can be good for students, esp. if they studied Python before.

我从来没有这样做过,也从来没有遇到过任何问题,但我可以看到它对学生有什么好处,尤其是。如果他们之前学习过 Python。

Const object on left side of comparison

比较左侧的 const 对象

Yoda conditions? No, please. It hurts readability. Just use the maximum warning level when you compile your code.

尤达条件?不谢谢。它损害了可读性。编译代码时只需使用最大警告级别。

Use unsigned for variables that are >= 0

对 >= 0 的变量使用无符号

OK. Funny enough, I've heard Stroustrup disagree.

好的。有趣的是,我听说 Stroustrup 不同意。

Set Pointer to NULL after deletion - Double delete protection

删除后将指针设置为 NULL - 双重删除保护

Bad advice! Never have a pointer which points to a deleted or non-existing object.

不好的建议!永远不要有指向已删除或不存在的对象的指针。

回答by Alok Save

it is more intuitive and easily understandable. It makes the intent clear.

它更直观,更容易理解。它使意图明确。

And it ensures that the code doesn't break when a new user might unknowingly miss the {, }while adding a new code statement.

并且它确保在新用户可能在不知情的情况下错过 时代码不会中断{}同时添加新的代码语句。

回答by Component 10

To add to the very sensible suggestions above, one example I encountered while refactoring some code of where this becomes critical was as follows: I was altering a very large codebase to switch from one API to another. The first API had a call to set Company Id as follows:

为了补充上述非常明智的建议,我在重构一些代码时遇到的一个例子如下所示:我正在修改一个非常大的代码库以从一个 API 切换到另一个 API。第一个 API 调用以如下方式设置公司 ID:

setCompIds( const std::string& compId, const std::string& compSubId );

whereas the replacement needed two calls:

而替换需要两次调用:

setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );

I set about changing this using regular expressions which was very successful. We also passed the code through astyle, which really made it very much more readable. Then, part way through the review process, I discovered that in some conditional circumstances it was changing this:

我开始使用非常成功的正则表达式来改变它。我们还通过astyle传递了代码,这确实使它更具可读性。然后,在过程中,我发现在某些有条件的情况下,它正在改变这一点:

if ( condition )
   setCompIds( compId, compSubId );

To this:

对此:

if ( condition )
   setCompId( compId );
setCompSubId( compSubId );

which is clearly not what what was required. I had to go back to the beginning do this again by treating the replacement as completely within a block and then manually altering anything that ended up looking goofy (at least it wouldn't be incorrect.)

这显然不是所需要的。我不得不重新开始,将替换完全视为一个块内的替换,然后手动更改任何最终看起来很傻的东西(至少它不会是不正确的。)

I notice that astylenow has the option --add-bracketswhich allows you to add brackets where there are none and I strongly recommend this if you ever find yourself in the same position as I was.

我注意到astyle现在有一个选项--add-brackets,允许你在没有括号的地方添加括号,如果你发现自己和我处于相同的位置,我强烈推荐这个。

回答by Lukasz Madon

I am using {}everywhere except a few cases where it's obvious. Single line is one of the cases:

{}除了少数很明显的情况外,我到处都在使用。单行是其中一种情况:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 

It may hurt you when you add some method before return. Indentation indicates that return is executing when condition is met, but it will return always.

在返回之前添加一些方法可能会伤害您。缩进表示在满足条件时返回正在执行,但它将始终返回。

Other example in C# with using statment

C# 中使用 statment 的其他示例

using (D d = new D())  // OK
using (C c = new C(d))
{
    c.UseLimitedResource();
}

which is equivalent to

这相当于

using (D d = new D())
{
    using (C c = new C(d))
    {
        c.UseLimitedResource();
    }
}

回答by KeithS

The most pertinent example I can think of:

我能想到的最相关的例子:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();

Which ifwill the elsebe paired with? Indentation implies that the outer ifgets the else, but that's not actually how the compiler will see it; the innerifwill get the else, and the outer ifdoesn't. You would have to know that (or see it behave that way in debugging mode) to figure out by inspection why this code might be failing your expectations. It gets more confusing if you know Python; in that case you know that indentation defines code blocks, so you would expect it to evaluate according to the indentation. C#, however, doesn't give a flying flip about whitespace.

if将与哪个else配对?缩进意味着外部if获取else,但实际上编译器不会看到它;该if将得到else和外if没有。您必须知道(或在调试模式下看到它的行为方式)才能通过检查找出为什么此代码可能未达到您的期望。如果你懂 Python,那就更容易混淆了;在这种情况下,您知道缩进定义了代码块,因此您希望它根据缩进进行评估。但是,C# 不会对空白进行快速翻转。

Now, that said, I don't particularly agree with this "always use brackets" rule on its face. It makes code very vertically noisy, reducing the ability to read through it quickly. If the statement is:

现在,话虽如此,我并不特别同意这种“始终使用括号”的规则。它使代码在垂直方向上非常嘈杂,从而降低了快速阅读的能力。如果语句是:

if(someCondition)
   DoSomething();

... then it should be written just like this. The statement "always use brackets" sounds like "always surround mathematical operations with parentheses". That would turn the very simple statement a * b + c / dinto ((a * b) + (c / d)), introducing the possibility of missing a close-paren (the bane of many a coder), and for what? The order of operations is well-known and well-enforced, so the parentheses are redundant. You'd only use parentheses to enforce a different order of operations than would normally be applied: a * (b+c) / dfor instance. Block braces are similar; use them to define what you want to do in cases where it differs from the default, and is not "obvious" (subjective, but usually pretty common-sense).

……那么就应该这样写。“总是使用方括号”这句话听起来像是“总是用括号将数学运算括起来”。这将把非常简单的语句a * b + c / d变成((a * b) + (c / d)),引入了缺少关闭括号(许多编码器的祸根)的可能性,为了什么?操作的顺序是众所周知的并且执行得很好,所以括号是多余的。您只能使用括号来强制执行与通常应用不同的操作顺序:a * (b+c) / d例如。块大括号类似;使用它们来定义在与默认值不同且不“明显”(主观,但通常是常识)的情况下您想要做什么。