Ответ 1
(1) Используется ли это правило для деструкторов?
Да, это правило применяется к деструкторам (исключение из правила для деструкторов), поэтому этот пример плохо сформирован. Чтобы сделать его хорошо сформированным, спецификация исключения ~D()
должна быть совместима с спецификацией ~B()
, например,
struct B {
virtual ~B() throw() { }
};
struct D : B {
virtual ~D() throw() { }
};
(2) Как это правило применяется к неявно объявленной специальной функции-члену?
В стандарте С++ говорится о неявно объявленных специальных функциях-членах:
Неявно объявленная специальная функция-член должна иметь спецификацию исключения.
Если
f
- неявно объявленный конструктор по умолчанию, оператор-конструктор копирования, деструктор или копирование, его неявная спецификация исключения указывает тип-идентификаторT
тогда и только тогда, когда исключениеT
спецификация функции, непосредственно вызываемойf
s неявным определение;
f
должен допускать все исключения, если любая функция, которую он вызывает напрямую, разрешает все исключения, аf
не допускает исключений, если каждая функция, которую он вызывает напрямую, не допускает исключений (С++ 03 § 15.4/13).
Какие функции непосредственно вызывают неявно объявленный деструктор?
После выполнения тела деструктора и уничтожения любых автоматических объектов, выделенных в теле, деструктор для класса
X
вызывает
- деструкторы для
X
s прямых членов,- деструкторы для прямых базовых классов
X
s и- Если
X
- тип самого производного класса, его деструктор вызывает деструкторы для <базовых классовX
s(С++ 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_;
};
Хотя эмпирическое правило никогда не должно писать спецификацию исключения, существует, по крайней мере, один общий случай, когда это необходимо.