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