2013-11-28 5 views
5

Почему это работает?C++ исключение наследования неоднозначность

#include <exception> 
#include <iostream> 
#include <stdexcept> 

#include <boost/exception/all.hpp> 

struct foo_error : virtual boost::exception, public std::runtime_error 
{ 
    explicit foo_error(const char* what) 
     : std::runtime_error(what) 
    { } 

    explicit foo_error(const std::string& what) 
     : std::runtime_error(what) 
    { } 
}; 

struct bar_error : virtual boost::exception, public std::runtime_error 
{ 
    explicit bar_error(const char* what) 
     : std::runtime_error(what) 
    { } 

    explicit bar_error(const std::string& what) 
     : std::runtime_error(what) 
    { } 
}; 

struct abc_error : virtual foo_error, virtual bar_error 
{ 
    explicit abc_error(const char* what) 
     : foo_error(what), bar_error(what) 
    { } 

    explicit abc_error(const std::string& what) 
     : foo_error(what), bar_error(what) 
    { } 
}; 

static void abc() 
{ 
    throw abc_error("abc error"); 
} 

int main() 
{ 
    try 
    { 
     abc(); 
    } 
    catch (const std::exception& e) 
    { 
     std::cerr << e.what(); 
    } 
} 

Я думал, что это не должно составить из-за неоднозначного перехода от abc_error к std::exception. Что мне не хватает? Я придумал диаграмму наследования, и я не могу понять, почему этот код работает (стрелки обозначают виртуальное наследование, а строки обозначают не виртуальное наследование).

std::exception     std::exception 
     +        + 
     |        | 
     |        | 
     +        + 
std::runtime_error    std::runtime_error 
     +        + 
     |        | 
     | +-->boost::exception<-+  | 
     + |      |  + 
    foo_error+<-----+   +--->+bar_error 
        |   | 
        |   | 
        |   | 
        +abc_error+ 

Похоже abc_error включает в себя два экземпляра std::exception так catch (или мне так казалось), не должен быть в состоянии бросить abc_error в std::exception. Или это должно быть?

UPDATE

Я не могу ответить на мои собственные вопросы на данный момент, поэтому я буду продолжать здесь. Я сузил проблему до:

struct NonVirtualBaseBase { }; 
struct NonVirtualBase : NonVirtualBaseBase { }; 

struct VirtualBase { }; 

struct A : virtual VirtualBase, NonVirtualBase { }; 
struct B : virtual VirtualBase, NonVirtualBase { }; 

struct C : A, B { }; 

int main() 
{ 
    try 
    { 
     throw C(); 
    } 
    catch (const VirtualBase& e) 
    { 
     return 1; 
    } 

    return 0; 
} 

Образец выше работает должным образом и является прекрасным фрагментом кода. Он сработает, если я заменил catch (const VirtualBase& e) на catch (const NonVirtualBase& e), который, по моему мнению, является нормальным и имеет смысл. Но он также работает, если я заменю ту же строку catch (const NonVirtualBaseBase& e), которая мне кажется странной и неправильной. Ошибка компилятора?

+0

+1 для хорошо отформатированного кода, хороший вопрос с объяснением и классный ASCII art :) –

+0

Какой компилятор это? – Agentlien

+0

@Agentlien Это Microsoft (R) C/C++ Оптимизация компилятора Версия 16.00.30319.01 для x64 –

ответ

2

UPDATE

Как отметил ОР, это объяснение не совсем сократить его, так как std::exception не является производным от использования виртуального наследования. Я считаю, что ответ заключается в том, что это на самом деле неопределенное поведение и просто не поймано во время компиляции, потому что нет необходимости в и catch знать друг о друге и предупреждать, если они несовместимы.

END UPDATE

Ответ в том, что эта иерархия использует * виртуальное наследование * вывести из boost::exception.

Так как foo_error и bar_error использует виртуальное наследование наследования от boost::exception, будет только один boost::exception основание, которое разделяется между обоими foo_error и bar_error суб-объектов в abc_error.

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

+0

Да, я бы не спросил, пытаюсь ли я поймать 'boost :: exception'. Но я пытаюсь «ловить» 'std :: exception', а не' boost :: exception', который включен через не виртуальное наследование. –

+0

@EgorTensin Я вижу это, сейчас .. Это хороший момент. – Agentlien

+0

Прошу прощения, это просто, что я не могу получить это прямо в моей голове! Сколько экземпляров 'std :: exception' включает экземпляр' abc_error'? Я думал, что это 2, один из 'foo_error' и один из' bar_error'. Следовательно, преобразование в 'std :: exception' должно быть неоднозначным. Я знаю, что преобразование в 'boost :: exception' в порядке, так как экземпляр' abc_error' включает только один экземпляр 'boost :: exception' (поскольку он наследуется' virtuallyly, а не как 'std :: runtime_error'). –

1

Почему это работает?

Для некоторых свободно определенных значений «работа».

[email protected] > g++ -o test test.cpp 
[email protected] > ./test 
terminate called after throwing an instance of 'abc_error' 
Aborted (core dumped) 
[email protected] > 
+0

Странно ... Я получил '> main.exe abc error' after '> cl/I" C: \ boost \ boost_1_55_0 "main.cpp' –

+0

Я получаю это от cl тоже. Странно это не :) –

1

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

Что НЕОБХОДИМО сделать, это правильно поймать исключение или, по крайней мере, это не должно. Но я могу видеть, как конкретный компилятор может закончиться (неправильно), делая это, из-за того, как совпадают объявления выражений.

Смежные вопросы