在“if”语句中初始化变量

81

我读到在C ++17中,我们可以像这样在if语句中初始化变量

if (int length = 2; length == 2)
    //execute something

不是

int length = 2;
if (length == 2)
    //do something

即使它更短,它也会影响代码的可读性(尤其是对于不了解这个新特性的人),我认为这是大型软件开发中的一种不良编码实践。

除了使代码更短之外,使用这个特性还有什么优点吗?


40
除了范围这件事情以外呢? - DeiDei
10
几年前,我猜有人说过:“我读到在C++11中,我们可以像这样创建lambda语句(...)。虽然它更简短,但它会影响代码的可读性(特别是对于不了解这个新特性的人),我认为这是大型软件开发的不良编码实践。” - R2RT
9
我认为它的长度完全相同,没有更短。 - user7860670
5
“纯粹的观点,因此不是答案:if (int length = 2; length == 2) 可能第一次看到会让你感到惊讶,但这并不是什么复杂的东西,第二次就不会太惊讶了,并且将声明的内容放在其所属的范围内是提高可读性的主要因素之一。在我看来,你的前提是错误的 ;)” - 463035818_is_not_a_number
14
对于不懂代码编写语言的人担心代码可读性(这就是“不熟悉这新特性”的意思)将是一场堕入泥潭的竞赛。 - user10762593
显示剩余6条评论
6个回答

98

它将length的作用域限制在if中。因此,您将获得与最初允许编写时相同的好处。

for(int i = 0; i < ... ; ++i) {
   // ...
}

不是变量泄漏

int i;
for(i = 0; i < ... ; ++i) {
   // ...
}
短暂的变量因为多种原因而更好。但是举几个例子:
  1. 某事物存在的时间越短,读取不相关代码行时需要记住的内容就越少。如果i在循环或if语句之外不存在,则我们不需要关心它们之外的值。我们也不需要担心其值将与程序的其他部分交互,这些部分超出了其预定范围(如果上述的i在另一个循环中被重复使用可能会发生这种情况)。这使得代码更易于理解和推理。
  2. 如果变量持有资源,则该资源现在仅被持有最短的时间。而且这是没有多余花括号的。它也清楚地表明该资源仅与单个 if 相关。考虑以下激励性的例子。

if(std::lock_guard _(mtx); guarded_thing.is_ready()) {
}
如果你的同事不知道这个功能,那就教给他们吧!迁就不愿意学习的程序员,是避免使用功能的借口。


13
我打算把这句话复制下来,放在一张两米长的海报上。 - Quentin
3
短寿命(作用域紧密)变量应该可以减少错误,因为一旦它们的目的达到后,您无法意外地在以后的代码中重复使用变量。 - Galik
2
或者使用弱指针:if (auto p = ptr.lock(); p && p->foo()) bar(*p); - Deduplicator
1
编译器可以在更多情况下重复使用堆栈空间。(例如,如果您将i通过引用或指针传递给外部函数。) - TLW
更好的问题可能是“相对于{int i = 2; if (i == 2) {...}},这有什么优势?”(注意额外的作用域。) - TLW

24

使用这个特性还有什么优势吗,除了让代码更短?

它可以缩小变量的作用域。这确实很有意义,并提高了可读性,因为它增强了你需要考虑的标识符的局部性。我同意要避免在 if 语句内部使用长的初始化语句,但对于短的内容,这是可以的。

注意,在 C++17 之前,您已经可以对结果进行初始化和分支:

int *get(); // returns nullptr under some condition

if (int *ptr = get())
    doStuff();

这取决于个人观点,但你可以认为明确的条件更易读:

if (int *ptr = get(); ptr != nullptr)
    doStuff();

此外,通过指出人们不习惯某个功能来反驳其可读性是危险的。有一段时间人们也不习惯使用智能指针,但我们今天都会认同(我猜)它们的好处。


4
因为定义了operator bool,所以你可以使用if (auto p = get()) - sudo rm -rf slash

20
新的if语句形式有很多用处。
目前,初始化器要么在语句之前声明并泄漏到环境范围中,要么使用显式作用域。有了新形式,这样的代码可以更紧凑地编写,并且改进的作用域控制使一些过去容易出错的构造变得更加健壮。 If语句初始化器的开放标准提案 输入图像描述 因此,总之,这个语句简化了常见的代码模式,并帮助用户保持作用域的紧密性。
希望对您有所帮助!

请问您能否明确一下您是在引用提案吗?特别是第二段。我建议使用块引用。 - StoryTeller - Unslander Monica
谢谢@StoryTeller,是的,我引用了open-std提案中包含在C++17中的第二段。 - Abhishek Sinha

10
为了最小化变量的作用范围,有一个习惯用语仅在创建时定义有效的资源(例如文件流对象):
if(auto file = std::ifstream("filename"))
{
    // use file here
}
else
{
    // complain about errors here
}

// The identifier `file` does not pollute the wider scope
有时您希望能够颠倒测试逻辑,使失败成为主要子句,而有效资源则成为else 子句。以前这是不可能的。但现在我们可以这样做:

有时您想要能够颠倒该测试的逻辑,使失败成为主要子句,而有效资源成为else子句。以前不可能做到这一点。但现在我们可以这样做:

if(auto file = std::ifstream("filename"); !file)
{
    // complain about errors here
}
else
{
    // use file here
}

一个例子可能是抛出异常:

if(auto file = std::ifstream(filename); !file)
    throw std::runtime_error(std::strerror(errno));
else
{
    // use file here
}

有些人喜欢编写代码,让函数在出现错误时尽早停止执行,否则继续执行。这种习惯将停止执行的逻辑放在继续执行的逻辑之前,一些人可能觉得这更加自然。


8

它在逻辑事件方面特别有用。考虑下面这个例子:

char op = '-';
if (op != '-' && op != '+' && op != '*' && op != '/') {
    std::cerr << "bad stuff\n";
}

看起来有点粗糙。除非您非常熟悉否定的OR,AND逻辑,否则您可能需要停下来思考这种逻辑,这通常是很差的设计。使用if初始化可以增加表达力。

char op = '-';
if (bool op_valid = (op == '-') || (op == '+') || (op == '*') || (op == '/'); !op_valid) {
    std::cerr << "bad stuff\n";
} 

命名的变量也可以在 if 语句内重复使用。例如:

if (double distance = std::sqrt(a * a + b * b); distance < 0.5){
    std::cerr << distance << " is too small\n";
}

这很棒,特别是考虑到变量是有作用域的,因此之后不会污染空间。


2
我知道这是主观的,但我强烈偏爱你的“粗略”版本,而不是带有if-initializer的版本。我发现它更容易阅读和理解。 - Fabio says Reinstate Monica
@FabioTurati 我想这是因为你非常熟悉它,而另一个版本是新的。但随着时间的推移,我希望if-initializer能够超越任何类似之处。 - Stack Danny

7

这是现有功能的扩展,据我体验它有助于阅读。

if (auto* ptr = get_something()) {
}

在这里,我们创建变量ptr并测试其非空。 ptr的范围仅限于其有效的范围内。让所有使用ptr都是有效的要容易得多。

但是,如果我们谈论的内容不以这种方式转换为 bool 呢?

if (auto itr = find(bob)) {
}

那不起作用。但是有了这个新功能,我们可以:

if (auto itr = find(bob); itr != end()) {
}

添加一个子句,指明“何时此初始化有效”。
实质上,这给了我们一组代表“初始化某个表达式,并在其有效时执行某些代码。当它无效时,则将其丢弃”的标记。
自C++98以来,进行指针测试技巧已成惯用语法。一旦您接受了这一点,这个扩展就很自然了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接