异常声明

相比于断言适用于排除逻辑上不可能存在的状态,异常通常是用于逻辑上可能发生的错误。

异常声明

Item 1:当函数不可能抛出异常或不能接受抛出异常时,使用noexcept

理由

如果不打算抛出异常的话,程序就会认为无法处理这种错误,并且应当尽早终止,如此可以有效地阻止异常的传播与扩散。

示例

//不可能抛出异常时
double compute(double d) noexcept
{
    return log(sqrt(d <= 0 ? 1 : d));
}

这里,已知compute不会抛出异常,因为它仅由不会抛出异常的操作所组成。 通过将compute声明为noexcept,让编译器和读者获得信息,使其更容易理解和操作compute。

注解

许多标准库函数都是noexcept的,这包括所有从C标准库中“继承”来的标准库函数,C++标准库隐含地为这些函数标上了noexcept。

示例

//不能接受抛出异常时
vector<double> munge(const vector<double>& v) noexcept
{
    vector<double> v2(v.size());
    // ... 做一些事 ...
}

这里的noexcept表明不希望或无法处理vector对象抛出异常的情形。认为内存耗尽是一种严重的设计错误(类比于硬件故障),因此希望当其发生时让程序崩溃。

注解

在大多数程序中,大多数函数都会抛出异常(比如说,
它们可能使用new,调用会抛出异常的函数,或者通过抛出异常
来报告失败的库函数),因此请勿随意到处散布noexcept而不考虑清楚异常是否可以被处理。

Item 2:C++11中析构函数和内存释放函数都被默认隐式地具备noexcept性质

理由

析构函数(无论是用户自定义的,还是编译器自动生成的)不能失败,若析构函数试图抛出异常来退出,这是一种设计错误,程序最好终止执行。

示例

struct A
{
	~A() {throw 1;}			//隐式地具备noexcept性质
}

struct B
{
	~B() noexcept(false) {throw 2;}	//noexcept(false),显式地声明为可以抛出异常
}

struct C
{
	B b;		//成员有noexcept(false)的析构函数,同样可以抛出异常
}

注解

如果程序员显示地为析构函数指定了noexcept,或者类的基类或成员有noexcept(false)的析构函数,析构函数就不会再保持默认值(比如这里的B和C)。上例中的类A析构函数被默认为noexcept(true),可以阻止异常的扩散,而B和C均可以抛出异常。

理由

delete函数常被析构函数所调用,C++11默认将delete函数设置成noexcept,从而提高应用程序的安全性。

示例

void operator delete (void *) noexcept;
void operator delete (void *) noexcept;

Item 3:对于constexpr函数,移动操作,swap函数,应该加上noexcept

理由

constexpr函数在运行时执行时可能抛出异常,因此可能需要对其中的一些使用noexcept

示例

//constexpr函数
constexpr int fac(int n) noexcept   
{
    return (n>1) ? n*fac(n-1) : 1;
}

理由

能够抛出异常的移动操作将违反大多数人的合理假设。不会抛出异常的移动操作可以更高效地被标准库和语言设施所利用。

示例

//移动操作

//正例
template<typename T>
class Vector 
{
	// ...
	Vector(Vector&& a) noexcept :elem{ a.elem }, sz{ a.sz } 
	{ 
		a.sz = 0; 
		a.elem = nullptr; 
	}
	Vector& operator=(Vector&& a) noexcept 
	{ 
		elem = a.elem;
		sz = a.sz; 
		a.sz = 0; 
		a.elem = nullptr; 
	}
	// ...
public:
	T * elem;
	int sz;
};
这些操作不会抛出异常。

//反例
template<typename T>
class Vector2 
{
	// ...
	Vector2(Vector2&& a) 
	{ 
		*this = a; // 直接利用复制操作
	}             
	Vector2& operator=(Vector2&& a) 
	{ 
		*this = a; // 直接利用复制操作
	}  
	// ...
public:
	T * elem;
	int sz;
};
Vector2 不仅低效,而且由于向量的复制需要分配内存而使其可能抛出异常。

理由

swap广泛地以假定永不失败的方式被使用,而且如果存在可能失败的 swap函数的话,程序也很难编写为可以正确工作。如果元素类型的 swap会失败的话,标准库的容器和算法也无法正确工作。

示例

//swap函数
class Foo 
{
// ...
public:
    void swap(Foo& rhs) noexcept
    {
        m1.swap(rhs.m1);
        std::swap(m2, rhs.m2);
    }
private:
    Bar m1;
    int m2;
};

为调用者方便起见,可以在类型所在的相同命名空间中提供一个非成员的 swap 函数。

void swap(Foo& a, Foo& b)
{
    a.swap(b);
}
posted @ 2020-08-26 19:46  心媛意码  阅读(213)  评论(0编辑  收藏  举报