14

C++ 标准规定了以下关于具有异常规范的虚函数:

如果虚函数具有异常规范,则在任何派生类中覆盖该虚函数的任何函数的所有声明(包括定义)都应仅允许基类虚函数的异常规范(C + +03 §15.4/3)。

因此,以下格式不正确:

struct B {
    virtual void f() throw() { } // allows no exceptions
};
struct D : B {
    virtual void f() { }         // allows all exceptions
};

(1) 此规则是否适用于析构函数?也就是说,下面的格式是否正确?

struct B {
    virtual ~B() throw() { }
};
struct D : B {
    virtual ~D() { }
};

(2) 该规则如何应用于隐式声明的析构函数?也就是说,下面的格式是否正确?

struct B {
    virtual ~B() throw() { }
};
struct D : B { 
    // ~D() implicitly declared
};

虽然在一般情况下永远不应该编写异常规范,但这个问题具有实际意义,因为std::exception析构函数是虚拟的并且具有空的异常规范。

由于不允许从析构函数中抛出异常是一种很好的做法,因此为了简化任何示例,我们假设析构函数要么允许所有异常(即,它没有异常规范),要么不允许异常(即也就是说,它有一个空的异常规范)。

4

1 回答 1

14

(1) 此规则是否适用于析构函数?

是的,这条规则适用于析构函数(析构函数的规则也不例外),所以这个例子是错误的。为了使其格式正确, 的异常规范~D()必须与 的 兼容~B(),例如,

struct B {
    virtual ~B() throw() { }
};
struct D : B {
    virtual ~D() throw() { }
};

(2) 该规则如何适用于隐式声明的特殊成员函数?

C++ 标准对隐式声明的特殊成员函数有以下说明:

隐式声明的特殊成员函数应具有异常规范。

iff是隐式声明的默认构造函数、复制构造函数、析构函数或复制赋值运算符,T当且仅当Tf的隐式定义直接调用的函数的异常规范允许时,其隐式异常规范指定类型 ID;

f如果它直接调用的任何函数允许所有异常,则应允许所有异常,并且f如果它直接调用的每个函数都不允许异常,则不应允许异常(C++03 §15.4/13)。

隐式声明的析构函数直接调用了哪些函数?

在执行析构函数的主体并销毁主体内分配的任何自动对象后,用于类X调用的析构函数

  • X的直接成员的析构函数,
  • X的直接基类的析构函数和,
  • 如果X是最派生类的类型,则其析构函数调用X的虚拟基类的析构函数

(C++03 §12.4/6;重新格式化以便于阅读)。

因此,隐式声明的析构函数有一个异常规范,允许任何这些析构函数允许的任何异常。考虑问题中的示例:

struct B {
    virtual ~B() throw() { }
};
struct D : B { 
    // ~D() implicitly declared
};

隐式声明调用的唯一析构函数~D()~B(). 因为~B()不允许异常,~D()不允许异常,就好像它被声明了一样virtual ~D() throw()

这个异常规范显然与~B()'s 兼容,所以这个例子是格式良好的。


作为为什么这很重要的一个实际示例,请考虑以下内容:

struct my_exception : std::exception {
    std::string message_;
};

~string()允许所有异常,因此隐式声明的~my_exception()允许所有异常。基类析构函数 ,~exception()是虚函数,不允许出现异常,因此派生类析构函数与基类析构函数不兼容,而且格式不正确。

为了使这个示例格式正确,我们可以使用空异常规范显式声明析构函数:

struct my_exception : std::exception {
    virtual ~my_exception() throw() { }
    std::string message_;
};

虽然经验法则是永远不要编写异常规范,但至少在这种常见情况下这样做是必要的。

于 2010-07-12T23:33:05.467 回答