class 成员初始值设定项抛出的异常是否应该调用 std::terminate()?
Should exceptions thrown from class member initializers call std::terminate()?
鉴于此代码:
struct A {
A(int e) { throw e; }
};
struct B {
A a{42}; // Same with = 42; syntax
};
int main() {
try {
B b;
} catch (int const e) {
return e;
}
}
使用 GCC(版本 4.7.4、4.8.5、4.9.3、5.4.0、6.3.0)编译时:
$ g++ -std=c++11 test.cpp -o test; ./test ; echo $?
terminate called after throwing an instance of 'int'
Aborted
134
但是当使用 Clang(版本 4.0.0)编译时:
$ clang++ -std=c++11 test.cpp -o test; ./test ; echo $?
42
哪种行为是正确的?
这是 GCC (Bug 80683) 中的错误。
如果构造函数是 try/catch
子句中的第一个操作,那么编译器认为它在它之外,尽管它应该包含它。
例如,以下工作正常:
#include <iostream>
struct A {
A(int e) { throw e; }
};
struct B {
A a{42}; // Same with = 42; syntax
};
int main() {
try {
// The following forces the compiler to put B's contructor inside the try/catch.
std::cout << "Welcome" << std::endl;
B b;
} catch (int e) {
std::cout << "ERROR: " << e << std::endl; // This is just for debugging
}
return 0;
}
运行:
g++ -std=c++11 test.cpp -DNDEBUG -o test; ./test ; echo $?
输出:
Welcome
ERROR: 42
0
我的猜测是,由于编译器优化,它将构造函数移动到主函数的开头。它假设 struct B
没有构造函数,然后假设它永远不会抛出异常,因此将它移到 try/catch
子句之外是安全的。
如果我们将 struct B
的声明更改为 显式 使用 struct A
构造函数:
struct B {
B():a(42) {}
A a;
};
那么结果将如预期的那样,我们将输入 try/catch
,即使在删除 "Welcome" 打印输出时也是如此:
ERROR: 42
0
Clang 是正确的。可以在 C++ 标准参考草案 n4296(强调我的)中找到关于它的参考资料:
15.3 Handling an exception [except.handle]
...
3 A handler is a match for an exception object of type E if
(3.1) — The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers),
此处抛出 int
,处理程序声明 const int
。存在匹配项,应调用处理程序。
鉴于此代码:
struct A {
A(int e) { throw e; }
};
struct B {
A a{42}; // Same with = 42; syntax
};
int main() {
try {
B b;
} catch (int const e) {
return e;
}
}
使用 GCC(版本 4.7.4、4.8.5、4.9.3、5.4.0、6.3.0)编译时:
$ g++ -std=c++11 test.cpp -o test; ./test ; echo $?
terminate called after throwing an instance of 'int'
Aborted
134
但是当使用 Clang(版本 4.0.0)编译时:
$ clang++ -std=c++11 test.cpp -o test; ./test ; echo $?
42
哪种行为是正确的?
这是 GCC (Bug 80683) 中的错误。
如果构造函数是 try/catch
子句中的第一个操作,那么编译器认为它在它之外,尽管它应该包含它。
例如,以下工作正常:
#include <iostream>
struct A {
A(int e) { throw e; }
};
struct B {
A a{42}; // Same with = 42; syntax
};
int main() {
try {
// The following forces the compiler to put B's contructor inside the try/catch.
std::cout << "Welcome" << std::endl;
B b;
} catch (int e) {
std::cout << "ERROR: " << e << std::endl; // This is just for debugging
}
return 0;
}
运行:
g++ -std=c++11 test.cpp -DNDEBUG -o test; ./test ; echo $?
输出:
Welcome
ERROR: 42
0
我的猜测是,由于编译器优化,它将构造函数移动到主函数的开头。它假设 struct B
没有构造函数,然后假设它永远不会抛出异常,因此将它移到 try/catch
子句之外是安全的。
如果我们将 struct B
的声明更改为 显式 使用 struct A
构造函数:
struct B {
B():a(42) {}
A a;
};
那么结果将如预期的那样,我们将输入 try/catch
,即使在删除 "Welcome" 打印输出时也是如此:
ERROR: 42
0
Clang 是正确的。可以在 C++ 标准参考草案 n4296(强调我的)中找到关于它的参考资料:
15.3 Handling an exception [except.handle]
...
3 A handler is a match for an exception object of type E if
(3.1) — The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers),
此处抛出 int
,处理程序声明 const int
。存在匹配项,应调用处理程序。