Google C++每周贴士 #165: 带初始化器的`if`和`switch`语句

(原文链接:https://abseil.io/tips/165 译者:clangpp@gmail.com)

每周贴士 #165: 带初始化器的ifswitch语句

如果你用不到条件控制流程,那可以到此为止了。

一个新语法

C++17 允许 ifswitch语句包含初始化器:

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");
}

这种复杂的考量催生了常见的程序员黑话:变量名长度应该与作用域大小相匹配;也就是说,作用域越大,其中的变量名就该越长(为了让读者走出二里地还能记着它)。反之,作用域越小,其中的变量名就可以越短。当变量名“泄露”的时候(如上所示),我们经常看见坑爹的模式涌现出来,例如:多个变量it1it2,…变得必要以防止命名冲突;变量被重新赋值(auto it = m1.find(/* ... */); it = m2.find(/* ... */));或变量有个侵入式的长名字(auto database_index_iter = m.find(/* ... */))。

细节,作用域,声明区

这个新的,可选的ifswitch语句中的初始化器,其工作方式与for语句里的初始化器完全一致。(后者基本是一个带初始化器的while语句。)也就是说,这个含有初始化器的语法基本就是如下写法的语法糖:

语法糖格式(译者注:自动)改写成为
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分支中仍然有效。

当然,还是有一点不同:在语法糖格式里,初始化器跟条件(condition)或主体(body)(既包含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重定义了,在初始化器中定义过了
}

与结构化绑定(structured bindings)的交互

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新引入的 节点句柄node handles),可以用来在map和set之间移动元素(而不是拷贝)。这个特性定义了一个可解构的 插入返回值insert-return-type),作为插入一个节点句柄的返回值。

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 (init; cond)switch (init; cond)语法:当你需要一个新变量,在ifswitch语句内部用到,且在此之外不被用到的时候。这可以简化周围的代码。另外,变量的作用域小了,变量名也可以起得更短。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值