Как спецификация исключений влияет на переопределение виртуального деструктора? - PullRequest
13 голосов
/ 13 июля 2010

Стандарт 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виртуальный и имеет пустую спецификацию исключений.

Поскольку эффективная практика состоит в том, чтобы не допускать исключения из деструктора, давайте предположим для упрощения любых примеров, что деструктор также разрешает все исключения (то есть, он не имеет спецификации исключений) или не допускает исключений (то есть имеет пустую спецификацию исключений).

1 Ответ

13 голосов
/ 13 июля 2010

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

Хотя эмпирическое правило никогда не должно писать спецификацию исключений, существует по крайней мере один распространенный случай, когда это необходимо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...