我有一个类似于这样的函数模板:
template <typename T>
constexpr auto myfunc() noexcept
{
return T{};
}
因为复制省略,这个函数模板是否保证是noexcept的?如果在构造函数内部抛出异常,这会发生在函数内部还是外部?
我有一个类似于这样的函数模板:
template <typename T>
constexpr auto myfunc() noexcept
{
return T{};
}
因为复制省略,这个函数模板是否保证是noexcept的?如果在构造函数内部抛出异常,这会发生在函数内部还是外部?
template <typename T>
constexpr auto myfunc() noexcept
{
return T{};
}
class xx {
public:
xx() { throw "Foo"; }
};
int main()
{
try {
myfunc<xx>();
} catch (...) {
}
}
结果:
terminate called after throwing an instance of 'char const*'
class xx {
public:
xx() { }
xx(xx &&) { throw "Foo"; }
xx(const xx &) { throw "Baz"; }
};
这段代码可以顺利执行,没有出现异常。
返回值的初始化发生在被调用者的上下文中(包含return
语句的函数)。也就是说,如果您想保留处理由T
的默认构造函数抛出的异常的可能性,您不应该使用noexcept
声明myfunc
。
我理解混淆的来源:根据C++17及更高版本中的值类别分类法,prvalue是构造对象的配方,而不是对象本身。请考虑以下代码:
T foo() {
return {};
}
T t = foo();
return
语句和t
的初始化是两个独立的步骤,尽管可以通过省略作为优化。在第一步中,返回对象(也称为"foo()
")从{}
进行复制初始化。在第二步中,t
从该返回对象进行复制初始化。显然,第一步发生在调用者上下文中,第二步发生在调用者上下文中。foo()
是一个prvalue,您可能认为return
语句仅创建一个配方(可以概念上表示为[](void* p) { new (p) T{}; }
),并且该配方在调用者上下文中创建,而执行该配方以创建t
将在调用者上下文中发生。如果是这种情况,则对T
的默认构造函数的实际调用将发生在调用者的上下文中,因此任何由它引发的异常都不会遇到调用者的外部大括号。t
的初始化是由return
语句本身完成的。这意味着在调用者的最外层块实际上离开之前,t
已经完全初始化。例如,如果调用方中有任何需要销毁的局部变量,则在t
已经初始化之后才会发生(因此,此行为与C++14的行为可能不同)。正如清楚地表明这样的局部变量的销毁发生在调用方上下文中(因此,如果抛出异常,则搜索处理程序的位置将遇到foo
的最外层块),t
的初始化也发生在调用方上下文中。像这样做:
template <typename T> constexpr
auto myfunc() noexcept(std::is_nothrow_default_constructible_v<T>)
{
return T{};
}