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}