C++17之noexcept作为类型的一部分

30 篇文章 17 订阅

在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的地方。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值