if
和switch
语句现在允许在条件表达式里添加一条初始化语句。
例如,你可以写出如下代码:
if (status s = check(); s != status::success) {
return s;
}
其中的初始化语句
status s = check();
初始化了s
,s
将在整个if
语句中有效(包括else
分支里)。
在if
语句的条件表达式里定义的变量将在整个if
语句中有效
(包括 then 部分和 else 部分)。例如:
if (std::ofstream strm = getLogStrm(); coll.empty()) {
strm << "<no data>\n";
}
else {
for (const auto& elem : coll) {
strm << elem << '\n';
}
}
// strm不再有效
在整个if
语句结束时strm
的析构函数会被调用。
另一个例子是关于锁的使用,假设我们要在并发的环境中执行一些依赖某个条件的任务:
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<std::mutex> lg{collMutex};
if (!coll.empty()) {
std::cout << coll.front() << '\n';
}
}
细微的区别在于前者中lg
在if
语句的作用域之内定义,
和条件语句在相同的作用域。
注意这个特性的效果和传统for
循环里的初始化语句完全相同。
上面的例子中为了让lock_guard
生效,必须在初始化语句里明确声明一个变量名,
否则它就是一个临时变量,会在创建之后就立即销毁。因此,初始化一个没有变量名的临时
lock_guard
是一个逻辑错误,因为当执行到条件语句时锁就已经被释放了:
if (std::lock_guard<std::mutex>{collMutex}; // 运行时ERROR
!coll.empty()) { // 锁已经被释放了
std::cout << coll.front() << '\n'; // 锁已经被释放了
}
原则上讲,使用简单的_
作为变量名就已经足够了:
if (std::lock_guard<std::mutex> _{collMutex}; // OK,但是...
!coll.empty()) {
std::cout << coll.front() << '\n';
}
你也可以同时声明多个变量,并且可以在声明时初始化:
if (auto x = qqq1(), y = qqq2(); x != y) {
std::cout << "return values " << x << " and " << y << "differ\n";
}
或者:
if (auto x{qqq1()}, y{qqq2()}; x != y) {
std::cout << "return values " << x << " and " << y << "differ\n";
}
另一个例子是向map
或者unordered 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';
}
这里,我们用了结构化绑定给返回值的成员和pos
指向的值的成员声明了新的名称,
而不是直接使用first
和second
成员。在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
语句,我们可以在对条件表达式求值之前初始化一个对象/实体。
例如,我们可以先声明一个文件系统路径,然后再根据它的类别进行处理:
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;
}
这里,初始化的路径p
可以在整个switch
语句中使用。