Наследование конструкторов и виртуальных базовых классов

Я собираюсь создать иерархию классов исключений, которая концептуально выглядит примерно так:

#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), но он явно не охватывает ваш случай.

Другие вопросы по тегам