本周小贴士#165:带有初始化器的if和switch语句

作为TotW#158最初发表于2019年8月17日

由Thomas Köppe创作

除非你使用条件控制流,否则你可以停止阅读了。

一种新语法

C++17允许if和swith语句包含初始化器:

if (init; cond) { /* ... */ }
switch (init; cond) { /* ... */ }

此语法让你能够尽可能地使变量作用域变小:

if (auto it = m.find("key"); it != m.end()) {
  return it->second;
} else {
  return absl::NotFoundError("Entry not found");
}

这种初始化语义与for语句完全相同;详见下文。

这种语法什么时候有用

管理复杂性的最重要方法之一是将复杂系统分解为非交互式的本地部分,这些部分可以单独理解并在整体上忽略。在C++中,变量的存在增加了复杂性,并且作用域允许我们限制此复杂性的范围:变量的作用域越小,读者记住该变量的次数就越少。

因此,在需要引起读者注意时,将变量的作用域限制在实际需要它们的地方是有价值的。新语法为此提供了一个新工具。然后将此新语法与C++17之前的备选代码进行比较:要么我们保持作用域的紧密,因此需要编写附加括号:

{
  auto it = m.find("key");
  if (it != m.end()) {
    return it->second;
  } else {
    return absl::NotFoundError("Entry not found");
  }
}

或者,似乎更典型的解决方案是不保持作用域的紧密性,只是“暴露”变量:

auto it = m.find("key");
if (it != m.end()) {
  return it->second;
} else {
  return absl::NotFoundError("Entry not found");
}

复杂性考虑导致了这条普遍箴言:变量名称长度应与变量作用域的大小相匹配;也就是说,在作用域时间更长的变量应该有更长的名称(因为它们需要对此已经过去很长时间的读者有意义)。反之,较小的作用域允许使用更短的名称。当变量名称泄漏(如上所述)时,我们看到出现不幸的模式,例如:多个变量it1、it2等必须成为必要的以避免冲突;变量被重新分配(auto it = m1.find(/* … /); it = m2.find(/ … /); 或变量获得具有侵入性的长名称(auto database_index_iter = m.find(/ … */))。

细节、作用域、声明区域

if和switch语句中的新可选初始化器与for循环语句中的初始化器完全相同。(后者基本上是一个带有初始化器的while语句。)也就是说,带有初始化器的语法大多只是以下重写的语法糖:

Sugared form Rewritten as
if (init; cond) BODY { init; if (cond) BODY }
switch (init; cond) BODY { init; switch (cond) BODY }
for (init; cond; incr) BODY { init; while (cond) { BODY; incr; } }

然而,有一个区别:在带有初始化器的语句中,初始化器与条件和主体(if分支和else分支)拥有相同的作用域,而不是在一个单独的较大作用域中。这意味着变量名称必须在所有这些部分中唯一,尽管它们可以遮盖先前的声明。以下示例说明了各种不允许重新声明和允许遮蔽声明的情况:

int w;

if (int x, y, z; int y = g()) {     // 错误:y在初始化器中已经声明
	int x;                          // 错误:x在初始化器中已经声明
	int w;                          // 正确:遮蔽外部变量
{
int x, y;                           // 正确:嵌套作用域中允许遮蔽
}
} else {
	int z;                          // 错误:z在初始化器中已经声明
}

if (int w; int q = g()) {           // 声明"w"正确,遮蔽外部变量
	int q;                          // 错误:q在条件中已经声明
	int w;                          // 错误:w在初始化器中已经声明
}

与结构化绑定交互

C++17还引入了结构化绑定,一种将名称分配给“可解构”值(如元组、数组或简单结构)中元素的机制:auto [iter, ins] = m.insert(/* … */);

这个特性与if语句中的新初始化器很好地配合使用:

if (auto [iter, ins] = m.try_emplace(key, data); ins) {
use(iter->second);
} else {
std::cerr << "Key '" << key << "' already exists.";
}

另一个示例来自于使用C++17的新节点处理程序,它允许在不复制的情况下在映射或集合之间真正地移动元素。该特性定义了一种插入返回类型,该类型是可解构的,并且由插入节点处理程序产生:

if (auto [iter, ins, node] = m2.insert(m1.extract(k)); ins) {
std::cout << "Element with key '" << k << "' transferred successfully";
} else if (!node) {
std::cerr << "Key '" << k << "' does not exist in first map.";
} else {
std::cerr << "Key '" << k << "' already in m2; m2 unchanged; m1 changed.";
}

结论
当您需要在if或switch语句内部使用一个新变量时,可以使用新的if (init; cond)和switch (init; cond)语法。这简化了环境代码。此外,由于变量的作用域现在很小,因此其名称也可以更短。

你可能感兴趣的:(C++,Tips,of,the,Week,开发语言,c++)