2013-11-28 39 views
5

Tại sao tính năng này hoạt động?C++ ngoại lệ thừa kế ngoại lệ

#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(); 
    } 
} 

Tôi nghĩ điều này không nên lập do chuyển đổi mơ hồ abc_error-std::exception. Tôi đang thiếu gì? Tôi đã đưa ra sơ đồ thừa kế, và tôi thực sự không thể hiểu tại sao mã này hoạt động (các mũi tên biểu thị thừa kế ảo và các dòng biểu thị thừa kế không ảo).

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

Dường như abc_error bao gồm hai trường hợp của std::exception nên catch (hoặc vì vậy tôi nghĩ) không nên có thể đúc abc_error-std::exception. Hay phải không?

CẬP NHẬT

tôi không thể trả lời câu hỏi của riêng tôi vào lúc này, vì vậy tôi sẽ tiếp tục ở đây. Tôi đã thu hẹp sự cố xuống:

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; 
} 

Mẫu ở trên hoạt động như mong đợi và là một đoạn mã hoàn hảo. Nó bị treo nếu tôi thay thế catch (const VirtualBase& e) bằng catch (const NonVirtualBase& e) mà tôi nghĩ là lành mạnh và có ý nghĩa. Nhưng nó cũng hoạt động nếu tôi thay thế cùng một dòng với catch (const NonVirtualBaseBase& e) mà với tôi có vẻ lạ và sai. Lỗi trình biên dịch?

+0

+1 cho mã được định dạng tốt, câu hỏi hay với lời giải thích và nghệ thuật ASCII mát mẻ :) –

+0

Trình biên dịch này là gì? – Agentlien

+0

@Agentlien Đó là Trình biên dịch tối ưu hóa Microsoft C/C++ Phiên bản 16.00.30319.01 cho x64 –

Trả lời

2

CẬP NHẬT

Như đã chỉ ra bởi OP, lời giải thích này không hoàn toàn cắt nó, vì std::exception là không bắt nguồn từ việc sử dụng thừa kế ảo. Tôi tin rằng câu trả lời cho rằng đây thực sự là hành vi không xác định và đơn giản là không bị bắt lúc biên dịch vì không cần throwcatch để biết lẫn nhau và cảnh báo nếu chúng không tương thích.

END CẬP NHẬT

Câu trả lời là hệ thống phân cấp này sử dụng * thừa kế ảo * để lấy được từ boost::exception.

Kể từ khi cả hai foo_errorbar_error sử dụng thừa kế ảo để kế thừa từ boost::exception, sẽ chỉ có một boost::exception cơ sở duy nhất mà được chia sẻ giữa cả foo_errorbar_error sub-đối tượng của một abc_error.

Khi bạn chỉ định virtual trước mục nhập trong danh sách lớp cơ sở, điều này có nghĩa là tất cả các lần xuất hiện của lớp này là lớp cơ sở ảo trong đối tượng xuất phát nhiều nhất sẽ thực sự tham chiếu đến cùng một cá thể. Nó được sử dụng đặc biệt để tránh sự mơ hồ trong kiểu thiết kế này.

+0

Vâng, tôi sẽ không hỏi tôi có cố gắng bắt 'boost :: exception' hay không. Nhưng tôi đang cố gắng để 'bắt'' std :: exception', không phải 'boost :: exception', được bao gồm thông qua thừa kế không ảo. –

+0

@EgorTensin Tôi thấy rằng, bây giờ .. Đó là một điểm tốt. – Agentlien

+0

Tôi xin lỗi, nó chỉ là tôi không thể có được điều này ngay trong đầu tôi! Có bao nhiêu trường hợp 'std :: exception' thực hiện một cá thể' abc_error' bao gồm? Tôi nghĩ đó là 2, một từ 'foo_error' và một từ' bar_error'. Do đó việc chuyển đổi sang 'std :: exception' sẽ không rõ ràng. Tôi biết rằng việc chuyển đổi sang 'boost :: exception' là OK, vì một cá thể' abc_error' chỉ bao gồm một cá thể 'boost :: exception' (vì nó được thừa hưởng' virtual'ly, không giống 'std :: runtime_error'). –

1

Tại sao tính năng này hoạt động?

Đối với một số giá trị được xác định lỏng lẻo của "công việc".

[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

Lạ ... Tôi đã nhận '> main.exe abc error' sau '> cl/I" C: \ boost \ boost_1_55_0 "main.cpp' –

+0

Tôi cũng nhận được từ cl. Lạ nó không phải là :) –

1

Nó có để biên dịch, vì không có lý do bạn không thể có các lớp học cơ sở nhân được xác định trong trường hợp ngoại lệ, và không có lý do bạn không thể có mà lớp cơ sở trong một tuyên bố ngoại lệ đâu đó. Thực tế là abc xảy ra để ném một điều cụ thể, và rằng main xảy ra để bắt một điều cụ thể, và rằng những điều đó là frenemies, không thể được biên dịch thời gian kiểm tra.

Những gì nó KHÔNG làm là bắt ngoại lệ đúng cách, hoặc ít nhất là không nên. Nhưng tôi có thể thấy làm thế nào một trình biên dịch cụ thể có thể kết thúc (không chính xác) làm điều đó, vì cách khai báo ngoại lệ được khớp.