2016-07-18 61 views
0

對於一個應用程序,我需要創建一組特殊的類來處理異常。我從std :: exception派生了我的基類。但是,我最終面臨鑽石問題和模糊的繼承。即使使用虛擬繼承也沒有幫助。以下示例演示了該問題。模糊不清的虛擬聯想

#include <iostream> 

class Exception: 
    public virtual std::exception 
{ 
public: 
    Exception(): 
     std::exception("This is default") 
    {   
     std::cout << "This is Exception\n"; 
    } 
}; 

class ChildException: 
    public virtual std::runtime_error, 
    public Exception 

{ 
public: 
    ChildException(): 
     Exception(), 
     std::runtime_error("hello") 
    { 
     std::cout << "This is ChildException\n"; 
    } 
}; 
int main() 
{ 
    ChildException exc();  
    //std::cout << static_cast<std::exception> (exc).what() << std::endl; 
    //std::cout << static_cast<std::runtime_error> (exc).what() << std::endl;  
    getchar(); 
    return 0; 
} 

該代碼編譯但無效。任何想法如何解決這個問題?

+1

*代碼編譯但不起作用* - 你是什麼意思不起作用? –

+4

'exc'已被聲明爲一個返回'ChildException'的函數。 – Brian

+4

只是一個挑剔的問題,但構造函數'std :: exception(const char * const&)'是一個非標準的擴展。雖然Visual Studio使用它,但我不確定是否有其他主要的編譯器。剛剛想到我現在指出了這一點,因爲如果你將來發現自己使用了不同的編譯器,它可能會回來咬你。 –

回答

0

這是一個解決鑽石問題的不同解決方案。在此解決方案中,基本Exception類不是從std :: exception派生的。相反,它包含一個指向std :: exception的指針。此外,它有兩個轉換器函數,使用它的指針將Exception轉換爲std :: exception。派生類使用std :: runtime_error來初始化指針。使用這種方法,解決了歧義問題,並且可以將一個ChildException對象捕獲爲std :: exception或std :: runtime_error。

#include <iostream> 

class Exception 
{ 
public:  
    explicit Exception() 
    { 
     pSTDException = nullptr; 
    } 
    operator std::exception*() 
    { 
     return pSTDException; 
    } 
    operator std::exception() 
    { 
     return * pSTDException; 
    } 
    std::exception * pSTDException; 
}; 

class ChildException: 
    public std::runtime_error, 
    public Exception 

{ 
public: 
    ChildException():   
     std::runtime_error("This is ChildException") 
    {   
     this->pSTDException = static_cast<std::exception *>(static_cast<std::runtime_error *>(this));   
    }  
}; 
int main() 
{  
    try 
    {   
     throw ChildException(); 
    }  
    catch(std::exception & e) 
    { 
     std::cout << e.what(); 
    } 
    getchar(); 
    return 0; 
} 
3

我可以看到目前的兩個問題:


1:最傷腦筋的解析

正如在評論Brian指出,這條線實際上是一個函數原型:

ChildException exc(); 

它可以被讀爲ChildException,其名稱爲exc通過調用默認構造函數來初始化,或者作爲名爲012的函數返回ChildException;我不確定確切的原因,但C++標準規定,在這種情況下,它將被視爲後者。

有三種方式來解決這個問題:

  • 刪除括號:如果你只是調用默認的構造函數,你可以把它寫不帶括號。然而,這並不總是一種選擇,因爲當你嘗試使用通過函數調用獲得的值進行直接初始化時,最令人頭疼的解析也會讓你無法應付。

    ChildException exc; 
    
    // Most vexing parse: 
    ChildException ce; 
    
    ChildException ce2(ce); 
        // This is safe, it can't be read as a function prototype. 
    ChildException ce3(ChildException()); 
        // This will be parsed as a function with: 
        // Return type: ChildException 
        // Parameter: Function pointer of type "ChildException (*)()". 
    
  • 使用拷貝初始化:您可以分配語法,編譯器會通過複製省略優化掉其初始化。

    ChildException exc = ChildException(); 
    

    這工作,但看起來不必要笨重,並且運行的效率較低,如果你遇到無法進行復制省略編譯器的風險。

  • 使用統一初始化:從C++ 11開始,當使用支持統一初始化*的編譯器時,可以使用大括號代替圓括號來指定構造函數調用;考慮到問題的標籤,我會推薦這種方法。

    ChildException exc{}; 
    

    * [出三個「最大」的編譯器,統一初始化由鏘3.1或更高版本的支持,GCC 4.6及更高版本,和Visual Studio 2013及更高版本。雖然GCC從4.4開始支持它,並且Visual Studio在2012 CTP中支持它,但在某些情況下,早期版本很難使用它;我不確定Clang的早期版本是否有問題。]


2:鑽石問題

我會假設你遇到麻煩的代碼是兩個註釋行:

//std::cout << static_cast<std::exception> (exc).what() << std::endl; 
//std::cout << static_cast<std::runtime_error> (exc).what() << std::endl; 

或者更具體地說,問題是這些行中的第一行導致「模糊轉換」錯誤,而第二行正常工作。這是因爲ChildException實際上有兩個std::exception基類,其中每個基類都與另一個分開。這個類的佈局看起來像這樣,具體是:

class ChildException size(28): 
    +--- 
    | +--- (base class Exception) 
0 | | {vbptr} 
    | +--- 
    +--- 
    +--- (virtual base runtime_error) 
    | +--- (base class exception) 
4 | | {vfptr} 
8 | | _Mywhat 
12 | | _Mydofree 
    | | <alignment member> (size=3) 
    | +--- 
    +--- 
    +--- (virtual base exception) 
16 | {vfptr} 
20 | _Mywhat 
24 | _Mydofree 
    | <alignment member> (size=3) 
    +--- 

注意,如果你願意,當你的Exception幾乎從std::exception繼承,std::runtime_error沒有。因此,其std::exception基地與您的Exceptionstd::exception基地截然不同,因此任何將ChildException轉換爲std::exception的嘗試都不明確,因爲它可能是指ChildException::Exception::exception基地或基地的基地。如果可能的話,我會建議重構你的異常類,以便每個異常類最多繼承一個異常類。如果這是不可能的,你可以通過一個基類投它:

// Cast into std::exception through the base classes: 
std::cout << "As Exception: " 
      << static_cast<std::exception>(static_cast<Exception>(exc)).what() 
      << std::endl; 

std::cout << "As runtime_error: " 
      << static_cast<std::exception>(static_cast<std::runtime_error>(exc)).what() 
      << std::endl; 

不建議這樣做,因爲造成鑽石問題的問題,但如果需要的話它是可用的。這是因爲其中每一個都會訪問不同的std::exception:第一個將在班級佈局結束時訪問virtual,第二個將訪問std::runtime_error中的一個。