【问题标题】:How does an exception specification affect virtual destructor overriding?异常规范如何影响虚拟析构函数覆盖?
【发布时间】:2010-07-12 23:32:30
【问题描述】:

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
};

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

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

【问题讨论】:

    标签: c++ exception exception-handling destructor exception-specification


    【解决方案1】:

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

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

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

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

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

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

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

    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()的兼容,所以这个例子格式正确。


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

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

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

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

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

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

    【讨论】:

    • 另外,从析构函数中抛出异常是个坏主意。
    • @Ben Voigt:同意;我在问题的最后一句话中说过。
    • 我想我已经养成了在答案中寻找类似细节的习惯,而不是问题。只要回答者比提问者聪明,这可能没问题。这导致了一个相当奇怪的推论:你比你自己聪明吗?您是如何在发布问题的同一分钟得到如此详细的答案的?
    • @Ben:我今天早上打算发布这个问题,然后我自己研究了答案,因为我很感兴趣(我真的不是特别熟悉异常规范的细节),所以我决定pull a GMan 并询问然后自己回答问题。我相当肯定,如果有的话,我比我自己笨:-)。我当然有兴趣看到这个问题的其他答案,特别是如果我错过了一些细节或一些密切相关的问题。
    • 我在使用 gcc 4.6 研究“宽松的抛出说明符”错误时偶然发现了这个问题。与您的 struct D : B 示例相反,gcc 的隐式析构函数似乎没有抛出声明。
    猜你喜欢
    • 1970-01-01
    • 2018-01-10
    • 2017-04-21
    • 2017-01-09
    • 2021-03-27
    • 2011-09-26
    • 1970-01-01
    • 2020-04-29
    • 2014-07-23
    相关资源
    最近更新 更多