Chapter2 带初始化的if和switch语句
if和switch语句现在允许在条件表达式里添加一条初始化语句。
例如,你可以写出如下代码:
if (status s = check(); s != status::success) {return s;}
其中的初始化语句
status s = check();
初始化了s,s将在整个if语句中有效(包括else分支里)。
2.1 带初始化的if语句
在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语句特性。
2.2 带初始化的switch语句
通过使用带初始化的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语句中使用。
