异常规范如何影响虚拟析构函数覆盖? [英] How does an exception specification affect virtual destructor overriding?

查看:288
本文介绍了异常规范如何影响虚拟析构函数覆盖?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C ++标准说明了具有异常规范的虚拟函数的以下内容:


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


因此,以下是不正确的:

  struct B {
virtual void f()throw(){} //允许没有异常
};
struct D:B {
virtual void f(){} //允许所有异常
};

(1)这个规则是否适用于析构函数?那就是以下形式好吗?

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

(2)这个规则如何应用于隐式声明的析构函数?那么,以下是否正确?

  struct B {
virtual〜B()throw(){ }
};
struct D:B {
//〜D()隐式声明
};

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



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

解决方案

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



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

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



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



C ++标准说明了关于隐式声明的特殊成员函数的以下内容:


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



如果 f 是一个隐式声明的默认构造函数,复制构造函数,析构函数或复制赋值运算符,其隐式异常-specification指定type-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()隐式声明
};

唯一的析构函数被隐式声明为〜D()〜B()。由于〜B()不允许异常,〜D()不允许异常,就好像它被声明 virtual〜D()throw()



这个异常规范显然与 〜B(),所以这个例子是格式正确的。






为什么这么重要的实际例子,请考虑以下内容:

  struct my_exception:std :: exception {
std ::字符串message_;
};

〜string()所以隐式声明的〜my_exception()允许所有异常。基类析构函数〜exception()是虚拟的,不允许异常,所以派生类析构函数与基类析构函数不兼容,这是不正确的。 / p>

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

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

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


The C++ Standard states the following about virtual functions that have exception specifications:

If a virtual function has an exception-specification, all declarations, including the definition, of any function that overrides that virtual function in any derived class shall only allow exceptions that are allowed by the exception-specification of the base class virtual function (C++03 §15.4/3).

Thus, the following is ill-formed:

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

(1) Does this rule apply to destructors? That is, is the following well-formed?

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

(2) How does this rule apply to an implicitly declared destructor? That is, is the following well-formed?

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

While in the general case one should never write an exception specification, this question has practical implications because the std::exception destructor is virtual and has an empty exception specification.

Since it is good practice not to allow an exception to be thrown from a destructor, let's assume for the sake of simplifying any examples that a destructor either allows all exceptions (that is, it has no exception specification) or it allows no exceptions (that is, it has an empty exception specification).

解决方案

(1) Does this rule apply to destructors?

Yes, this rule applies to destructors (there is no exception to the rule for destructors), so this example is ill-formed. In order to make it well-formed, the exception specification of ~D() must be compatible with that of ~B(), e.g.,

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

(2) How does this rule apply to implicitly declared special member function?

The C++ Standard says the following about implicitly declared special member functions:

An implicitly declared special member function shall have an exception-specification.

If f is an implicitly declared default constructor, copy constructor, destructor, or copy assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f’s implicit definition;

f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions (C++03 §15.4/13).

What functions are directly invoked by an implicitly declared destructor?

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls

  • the destructors for X’s direct members,
  • the destructors for X’s direct base classes and,
  • if X is the type of the most derived class, its destructor calls the destructors for X’s virtual base classes

(C++03 §12.4/6; reformatted for easier reading).

So, an implicitly declared destructor has an exception specification that allows any exceptions allowed by any of those destructors. To consider the example from the question:

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

The only destructor called by the implicitly declared ~D() is ~B(). Since ~B() allows no exceptions, ~D() allows no exceptions and it is as if it were declared virtual ~D() throw().

This exception specification is obviously compatible with ~B()'s, so this example is well-formed.


As a practical example of why this matters, consider the following:

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

~string() allows all exceptions, so the implicitly declared ~my_exception() allows all exceptions. The base class destructor, ~exception(), is virtual and allows no exceptions, so the derived class destructor is incompatible with the base class destructor and this is ill-formed.

To make this example well-formed, we can explicitly declare the destructor with an empty exception specification:

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

While the rule of thumb is never to write an exception specification, there is at least this one common case where doing so is necessary.

这篇关于异常规范如何影响虚拟析构函数覆盖?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆