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

На път съм да създам йерархия на клас изключение, която концептуално изглежда донякъде така:

#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, защото наследяващият конструктор не наследява от директна база.)


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

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

т.е. във вашия случай using-declaration в OperationFailedBecauseFileDoesNotExistError не наследява, а предекларира (като псевдоним) или показва името на ctor на ExceptionBase.

Ще трябва да напишете ненаследяващ ctor за OperationFailedBecauseFileDoesNotExistError.


Между другото, това е добре за невиртуални базови класове: декларацията за използване за наследяване на ctors е пренаписана като:

//using ExceptionBase::ExceptionBase;

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

Тъй като можете да инициализирате само директен базов клас (или виртуален базов клас) в mem-initializer-list, има смисъл за невиртуалните базови класове да ограничат using-declaration за наследяване на ctors само от директни базови класове.

Авторите на предложението за наследяване на 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
Ако тази using-декларация в 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