C++17 特性:带初始化的 if 和 switch

C++17中, ifswitch 控制语句允许在常规的条件或选择语句前指定一个初始化语句。

例如,可以这样写:


if (status s = check(); s != status::success) {
return s;
}

其中,初始化语句

status s = check();

初始化了变量 s ,该变量在整个 if 语句(包括可能的 else 语句)中有效。

  • 带初始化的 if

if 语句中任何初始化的值都是有效的,直到 then 结束或 else 部分(如果有的话)结束。

例如:


if (std::ofstream strm = getLogStrm(); coll.empty()) {
strm << “\n”;
}
else {
for (const auto& elem : coll) {
strm << elem << ‘\n’;
}
}
// strm 不再有效

strm的析构函数在 then 部分结尾或 else 部分结尾处被调用。

另一个例子是执行依赖于某条件的任务时加锁:


if (std::lock_guard<std::mutex> lg{collMutex}; !coll.empty()) {
std::cout << coll.front() << ‘\n’;
}

根据类模板参数推导,现在可以这样写:


if (std::lock_guard lg{collMutex}; !coll.empty()) {
std::cout << coll.front() << ‘\n’;
}

这段代码等价于:


{
std::lock_guard lg{collMutex};
if (!coll.empty()) {
std::cout << coll.front() << ‘\n’;
}
}

与在 if 语句的作用域内定义 lg 有点小区别,所以该条件判断是在相同作用域(可声明区域)内。

此特性与传统的 for 循环中的初始化语句一样的工作方式。任何被初始化的对象必须有一个名字贯穿整个控制结构。否则,初始化语句自己就创建一个临时的,并立即销毁。例如,初始化一个 lock guard 却不指定名字,就会不再锁住,当判断条件检查时:


if (std::lock_guard<std::mutex>{collMutex}; // 运行时错误:
!coll.empty()) { // 不再锁住
std::cout << coll.front() << ‘\n’; // 不再锁住
}

从原理上说,一个简单的像“_”这样的名字就足够了(有些程序员喜欢这样,有些不喜欢还不允许在全局名字空间内使用):


if (std::lock_guard<std::mutex> _{collMutex}; // 可以,但是…
!coll.empty()) {
std::cout << coll.front() << ‘\n’;
}

第三个例子,插入一个新元素到 map 或无序 map 中。可以检查是否插入成功:


std::map<std::string, int> coll;

if (auto [pos,ok] = coll.insert({“new”,42}); !ok) {
// 如果插入失败,则使用迭代器pos进行错误处理:
const auto& [key,val] = *pos;
std::cout << “already there: “ << key << ‘\n’;
}

这里,我们使用结构化绑定将返回的值和元素都绑定到可读性更好的名字上去,而不是用 firstsecond 。在C++17之前,相应的检查要这样做:


auto ret = coll.insert({“new”,42});
if (!ret.second){
// 如果插入失败,则通过迭代器ret.first进行错误处理
const auto& elem = *(ret.first);
std::cout << “already there: “ << elem.first << ‘\n’;
}

另外,这里可以用编译期 if 特性进行扩展。

  • 带初始化的switch

使用带初始化的 switch 允许我们在计算判断条件以继续进行控制流之前,初始化一个对象或变量,并在该 switch 作用域内可用。

例如,我们可以在根据路径处理 filesystem::path 前先初始化:


namespace fs = std::filesystem;

switch (fs::path p{name}; status(p).type()) {
case fs::file_type::not_found:
std::cout << p << ” not found\n”;
break;
case fs::file_type::directory:
std::cout << p << “:\n”;
for (const auto& e : std::filesystem::directory_iterator{p}) {
std::cout << “- “ << e.path() << ‘\n’;
}
break;
default:
std::cout << p << ” exists\n”;
break;

}

这里,被初始化的 path p 可在整个 switch 语句范围内使用。

以前的C++17相关文章:

长按二维码关注公众号“软件开发谈”