Skip to content

Latest commit

 

History

History
130 lines (124 loc) · 4.25 KB

ch02.md

File metadata and controls

130 lines (124 loc) · 4.25 KB

Chapter2 带初始化的ifswitch语句

ifswitch语句现在允许在条件表达式里添加一条初始化语句。

例如,你可以写出如下代码:

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

其中的初始化语句

status s = check();

初始化了ss将在整个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';
    }
}

细微的区别在于前者中lgif语句的作用域之内定义, 和条件语句在相同的作用域。

注意这个特性的效果和传统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指向的值的成员声明了新的名称, 而不是直接使用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语句特性。

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语句中使用。