Наследование конструкторов и виртуальных базовых классов
Я собираюсь создать иерархию классов исключений, которая концептуально выглядит примерно так:
#include <iostream>
#include <stdexcept>
class ExceptionBase : public std::runtime_error {
public:
ExceptionBase( const char * msg ) : std::runtime_error(msg) {}
};
class OperationFailure : virtual public ExceptionBase {
public:
using ExceptionBase::ExceptionBase;
};
class FileDoesNotExistError : virtual public ExceptionBase {
public:
using ExceptionBase::ExceptionBase;
};
class OperationFailedBecauseFileDoesNotExistError
: public OperationFailure, FileDoesNotExistError {
public:
using ExceptionBase::ExceptionBase; // does not compile
};
int main() {
OperationFailedBecauseFileDoesNotExistError e("Hello world!\n");
std::cout << e.what();
}
Все конструкторы должны выглядеть так же, как конструктор ExceptionBase
учебный класс. Производные исключения различаются только в отношении их типа, в противном случае никаких дополнительных функций нет. Последний тип исключения, упомянутый в приведенном выше коде, также должен иметь эти конструкторы. Возможно ли это с помощью функции наследования конструкторов стандарта C++11? Если это невозможно: каковы альтернативы?
(Кстати: в приведенном выше коде классы OperationFailure
а также FileDoesNotExistError
не компилируется с gcc 4.8, но с clang 3.4. По-видимому, gcc отклоняет наследуемые конструкторы для виртуальных баз. Было бы интересно узнать, кто здесь. Оба компилятора отклонили класс OperationFailedBecauseFileDoesNotExistError
потому что наследующий конструктор не наследуется от прямой базы.)
2 ответа
Когда декларация using используется для наследования конструкторов, требуется прямой базовый класс [namespace.udecl]/3.
Если такое объявление- использование называет конструктор, спецификатор nested-name- name должен называть прямой базовый класс определяемого класса; в противном случае он вводит набор объявлений, найденных при поиске имени члена.
Т.е. в вашем случае декларация об использовании в OperationFailedBecauseFileDoesNotExistError
не наследует, но повторно объявляет (как псевдоним) или отображает имя ctor ExceptionBase
,
Вам придется написать ненаследующий ctor для OperationFailedBecauseFileDoesNotExistError
,
Между прочим, это хорошо для не виртуальных базовых классов: объявление использования для наследования ctors переписано как:
//using ExceptionBase::ExceptionBase;
OperationFailure(char const * msg)
: ExceptionBase( static_cast<const char*&&>(msg) )
{}
Поскольку вы можете только инициализировать прямой базовый класс (или виртуальный базовый класс) в списке mem-initializer-list, для невиртуальных базовых классов имеет смысл ограничить объявление using для наследования ctors только от прямых базовых классов.
Авторы предложения о наследующих ctors знали, что это нарушает поддержку виртуальных ctors базового класса, см. N2540:
Как правило, наследование определений конструктора для классов с виртуальными базами будет некорректным, если только виртуальная база не поддерживает инициализацию по умолчанию или виртуальная база не является прямой базой и не названа в качестве переадресованной базы. Аналогично, все члены данных и другие прямые базы должны поддерживать инициализацию по умолчанию, иначе любая попытка использовать наследующий конструктор будет неверной. Примечание: плохо сформировано при использовании, не заявлено.
Наследование конструкторов похоже на введение функций-оболочек для всех указанных вами конструкторов. В вашем случае вы должны вызвать конкретные конструкторы обоих OperationFailure
а также FileDoesNotExistError
но представленные обертки будут вызывать только один из них.
Я только что проверил последний черновик C++11 (раздел 12.9), но он явно не охватывает ваш случай.