在c++ 17异常处理规范成为函数类型的一部分。也就是说,下面两个函数现在有两种不同的类型:
void f1();
void f2() noexcept; // different type
在c++ 17之前,这两个函数都具有相同的类型。
因此,编译器现在将检测如果你使用一个函数抛出异常,而一个函数不抛出任何异常的情况:
void (*fp)() noexcept; // pointer to function that doesn’t throw
fp = f2; // OK
fp = f1; // ERROR since C++17
当然,在允许抛出函数的地方使用不抛出的函数仍然是有效的:
void (*fp2)(); // pointer to function that might throw
fp2 = f2; // OK
fp2 = f1; // OK
因此,这个新特性不会破坏那些还没有使用noexcept函数指针的程序,但是现在可以确保您不再违反函数指针中的noexcept规范。
但是,不允许重载具有不同异常规范的同一签名的函数名(因为只允许重载具有不同返回类型的函数):
void f3();
void f3() noexcept; // ERROR
注意,所有其他规则都不受影响。例如,仍然不允许忽略基类的noexcept规范:
class Base {
public:
virtual void foo() noexcept;
...
};
class Derived : public Base {
public:
void foo() override; // ERROR: does not override
...
};
在这里,派生类中的成员函数foo()具有不同的类型,因此它不会覆盖基类的foo()。这段代码仍然不能编译。即使没有覆盖说明符,这段代码也无法编译,因为我们仍然不能使用更松散的抛出规范进行重载。
1. 使用条件异常规范
当使用条件noexcept规范时,函数的类型取决于条件是真还是假:
void f1();
void f2() noexcept;
void f3() noexcept(sizeof(int)<4); // same type as either f1() or f2()
void f4() noexcept(sizeof(int)>=4); // different type than f3()
这里,f3()的类型取决于编译代码时条件的类型:
- 如果sizeof(int)等于4(或大于4),则函数签名为
void f3() noexcept(false);//与f1()类型相同
- 如果sizeof(int)小于4,则函数签名为
void f3() noexcept(true); // same type as f2()
因为f4()的异常条件使用了f3()的相反表达式,所以f4()总是有不同的类型(即,它保证在f3()不抛出时抛出,反之亦然。
以前的throw规范仍然可以使用,但自从c++ 17以来就被弃用了:
void f5() throw(); // same as void f5() noexcept but deprecated
不再支持动态抛出规范(自c++ 11以来一直不支持):
void f6() throw(std::bad_alloc); // ERROR: invalid since C++17
2. 对通用库的影响
对于泛型库,只使用类型的except声明部分可能会产生一些后果。
例如,下面的程序在c++ 14之前是有效的,但是不再用c++ 17编译:
#include <iostream>
template<typename T>
void call(T op1, T op2)
{
op1();
op2();
}
void f1()
{
std::cout << "f1()\n";
}
void f2() noexcept
{
std::cout << "f2()\n";
}
int main()
{
call(f1, f2); // ERROR since C++17
}
问题是,由于c++ 17 f1()和f2()具有不同的类型,所以编译器在实例化函数模板调用()时不再为这两种类型找到一个公共类型T。
在c++ 17你必须使用两种不同的类型:
template<typename T1, typename T2>
void call(T1 op1, T2 op2)
{
op1();
op2();
}
如果希望或必须重载所有可能的函数类型,那么现在必须加倍重载。
例如,这适用于标准类型trait std::is_function<>的定义。定义了主模板,因此通常类型T没有函数:
// primary template (in general type T is no function):
template<typename T> struct is_function : std::false_type { };
模板派生自std::false_type,因此is_function<T>::value对于任何类型的T通常都会产生false。
对于所有属于函数的类型,都存在部分专门化,它们派生自std::true_type,使成员值为true:
// partial specializations for all function types:
template<typename Ret, typename... Params>
struct is_function<Ret (Params...)> : std::true_type { };
template<typename Ret, typename... Params>
struct is_function<Ret (Params...) const> : std::true_type { };
template<typename Ret, typename... Params>
struct is_function<Ret (Params...) &> : std::true_type { };
template<typename Ret, typename... Params>
struct is_function<Ret (Params...) const &> : std::true_type { };
......
在c++ 17之前,已经有24个局部专门化,因为函数类型可以有const和volatile限定符,也可以有lvalue(&)和rvalue(&&)引用限定符,而且需要重载带有可变参数列表的函数。
在c++ 17中,通过在所有这些局部专门化中添加noexcept限定符,局部专门化的数量增加了一倍,现在我们得到了48个局部专门化:
...
// partial specializations for all function types with noexcept:
template<typename Ret, typename... Params>
struct is_function<Ret (Params...) noexcept> : std::true_type { };
template<typename Ret, typename... Params>
struct is_function<Ret (Params...) const noexcept> : std::true_type { };
template<typename Ret, typename... Params>
struct is_function<Ret (Params...) & noexcept> : std::true_type { };
template<typename Ret, typename... Params>
struct is_function<Ret (Params...) const& noexcept> : std::true_type { };
...
没有实现noexcept重载的库可能不再编译代码,这些代码使用它们将函数或函数指针传递到不需要noexcept的地方。