C++:不带花括号就不算离开作用域?

std::thread

运行下面的代码会怎么样呢?

 1int main() {
 2    int i = 0;
 3    std::thread([&i] {
 4        std::cout << "hello" << std::endl;
 5        i = 1;
 6    });
 7    while (i == 0) {
 8        std::cout << "waiting" << std::endl;
 9    }
10    return 0;
11}

答案是崩溃了。不会有任何输出。要知道为什么,请看下文。

问题:下面的代码会出错吗?

1int main() {
2    4;
3    return 0;
4}

答案:不会。

下面的呢?

1class A {};
2
3int main() {
4    A;
5    return 0;
6}

会。因为栈上对象构造必须带括号。改成:

1class A {};
2
3int main() {
4    A{};
5    return 0;
6}

可以编译通过。那么请问,A 的作用域是?

问了几个人,都说是 main{} 之间。真的吗?

我们写个程序测试一下。下面在 A 的构造函数和解构函数的地方 log, 然后利用 std::atexit 在离开 main 后 log.

 1class A {
 2  public:
 3    explicit A() { std::cout << "A()" << std::endl; }
 4    ~A() { std::cout << "~A()" << std::endl; }
 5};
 6
 7int main() {
 8    std::atexit([] { std::cout << "after main" << std::endl; });
 9    std::cout << "main" << std::endl;
10    A();
11    std::cout << "to leave main" << std::endl;
12    return 0;
13}

如果按照之前的想法,输出应该是

1main
2A()
3to leave main
4~A()
5after main

运行一下,结果竟然是:

1main
2A()
3~A()
4to leave main
5after main

这似乎说明,A(); 的对象的作用域仅限于它所在的行。

如果改成:

1int main() {
2    std::atexit([] { std::cout << "after main" << std::endl; });
3    std::cout << "main" << std::endl;
4    auto a = A();
5    std::cout << "to leave main" << std::endl;
6    return 0;
7}

则输出变成

1main
2A()
3to leave main
4~A()
5after main

作用域提升到了 {} 之间。

真的是行吗?有没有可能更短?

1int main() {
2    std::atexit([] { std::cout << "after main" << std::endl; });
3    std::cout << "main" << std::endl;
4    std::cout << "[", A(), std::cout << "]";
5    std::cout << "to leave main" << std::endl;
6    return 0;
7}

输出为:

1main
2[A()
3]~A()
4to leave main
5after main

这样看来,临时对象作用域是从定义处开始,到语句结束。

再看一开始提到的代码:

 1int main() {
 2    int i = 0;
 3    std::thread([&i] {
 4        std::cout << "hello" << std::endl;
 5        i = 1;
 6    });
 7    while (i == 0) {
 8        std::cout << "waiting" << std::endl;
 9    }
10    return 0;
11}

while 执行之前,实际上就调用了 thread 的析构函数。因此线程传入的函数根本来不及执行。并且由于没有 join 或者 detach,还会引发 terminal.

解决方法很简单,给 thread 一个名字(当然,这样只是有显示了,但还需要 join 或者 detach 才能不崩溃),或者就地 join

1int main() {
2    std::thread([] {
3        std::cout << "hello" << std::endl;
4    }).join();
5    return 0;
6}