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

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

#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. Производные исключения отличаются только своим типом, в противном случае нет никакой дополнительной функциональности. Последний тип исключения, упомянутый в приведенном выше коде, также должен иметь эти конструкторы. Возможно ли это с помощью функции наследования конструкторов стандарта С++ 11? Если это невозможно: каковы альтернативы?

(Кстати: в приведенном выше коде классы OperationFailure и FileDoesNotExistError компилировались не с gcc 4.8, а с clang 3.4. Судя по всему, gcc отвергает наследование конструкторов для виртуальных баз. Было бы интересно узнать, кто здесь прав. Оба компилятора отвергли класс OperationFailedBecauseFileDoesNotExistError, потому что наследующий конструктор не наследуется от прямой базы.)


person Ralph Tandetzky    schedule 16.10.2013    source источник
comment
Бриллиантовое наследство? Милая.   -  person littleadv    schedule 16.10.2013
comment
Веселье начинается через 3, 2, 1...   -  person Adri C.S.    schedule 16.10.2013
comment
Может быть, вам нужно использовать using OperationFailure::OperationFailure? Но, вероятно, это не сработает из-за двойного наследования.   -  person Albert    schedule 16.10.2013
comment
Даже недавний g++4.9 не компилирует OperationFailure при создании объекта (с параметризованным ctor). При замене виртуального наследования на не виртуальное оно работает. Я предлагаю вам отправить отчет об ошибке для gcc, так как я не могу найти ничего в Стандарте, что запрещает это, и последнее предложение N2540 явно разрешает это.   -  person dyp    schedule 16.10.2013
comment
Я отправлю отчет об ошибке.   -  person Ralph Tandetzky    schedule 16.10.2013


Ответы (2)


Когда для наследования конструкторов используется using-declaration, требуется прямой базовый класс [namespace.udecl]/3.

Если такое объявление-использования называет конструктор, то вложенный-спецификатор-имени должен указать прямой базовый класс определяемого класса; в противном случае он вводит набор объявлений, найденных при поиске имени члена.

т.е. в вашем случае объявление использования в OperationFailedBecauseFileDoesNotExistError не наследует, а повторно объявляет (как псевдоним) или показывает имя ctor ExceptionBase.

Вам придется написать не наследующий ctor для OperationFailedBecauseFileDoesNotExistError.


Кстати, это нормально для невиртуальных базовых классов: декларация использования для наследования ctors переписывается так:

//using ExceptionBase::ExceptionBase;

OperationFailure(char const * msg)
: ExceptionBase( static_cast<const char*&&>(msg) )
{}

Поскольку вы можете инициализировать только прямой базовый класс (или виртуальный базовый класс) в mem-initializer-list, для невиртуальных базовых классов имеет смысл ограничить декларацию использования наследовать cторы только от прямых базовых классов.

Авторы предложения о наследовании ctors знали, что это нарушает поддержку виртуальных ctors базового класса, см. N2540:

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

person dyp    schedule 16.10.2013

Наследование конструкторов похоже на введение функций-оболочек для всех указанных вами конструкторов. В вашем случае вы должны вызывать конкретные конструкторы как OperationFailure, так и FileDoesNotExistError, но введенные оболочки будут вызывать только любой из них.


Я только что проверил последний проект C++11 (раздел 12.9), но на самом деле это явно не касается вашего случая.

person Albert    schedule 16.10.2013
comment
Неявное объявление ctors по умолчанию не запрещается using-declaration, поэтому OperationFailure и FileDoesNotExistError по-прежнему имеют ctors по умолчанию. Однако я не уверен, что они правильно сформированы, поскольку они неправильно инициализируют виртуальный базовый класс. - person dyp; 16.10.2013
comment
Если бы это объявление-использования в OperationFailedBecauseFileDoesNotExistError (давайте назовем этот класс O) было бы допустимым, оно бы представило ctor, эквивалентный O(char const* msg) : ExceptionBase( static_cast<char const*&&>(msg) ) {}. Однако этот ctor имеет неправильный формат, поскольку вызывает удаленный ctor по умолчанию обоих прямых базовых классов. Последние имеют неправильный формат (удалены), поскольку они не инициализируют виртуальный базовый класс, см. [class.ctor]/5. - person dyp; 16.10.2013
comment
(См. также DR1567). - person dyp; 16.10.2013