2013-02-18 62 views
1

我目前在學習C++,並通過實現一個簡單的AddressBook應用程序來練習我的知識。
我從一個Entry類和一個AddressBook類開始,它實現了一個STL Map來按照人的姓氏訪問條目。
現在我來到了下面的代碼:C++關於類設計異常處理的幫助

Entry AddressBook::get_by_last_name(string last_name){ 
    if(this->addr_map.count(last_name) != 0){ 
     //What can I do here? 
    } else { 
     return addr_map[last_name]; 
    } 

腳本語言,我只想返回類似-1, Error Message(Python中的列表),以表明函數失敗。我不想拋出異常,因爲它是應用程序邏輯的一部分。呼叫類應該能夠通過在控制檯上打印或打開消息框來響應請求。
現在我想通過向類Entry引入某種無效狀態來實現在C++中實現腳本語言方法。但是在C++中不是那麼糟糕的做法嗎?難道我的整個班級設計都不合適?我感謝任何幫助。請記住,我仍然在學習C++。

回答

1

一個簡單的選項是將返回類型更改爲Entry*(或const Entry*),然後返回條目的地址(如果找到),否則返回NULL。

如果您使用Boost,則可以返回boost::optional<Entry>,在這種情況下,您的成功代碼將相同,但在未找到時您會說return boost::none。這很有趣,但與使用指針返回類型大致相同。

+0

將提升::可選更改我返回的類型的大小?我的意思是它必須在任何地方保存額外的信息? – 2013-02-18 12:12:17

+0

如果使用的是相當近的編譯器,則使用'nullptr'而不是'NULL'。這是一個c + + 11關鍵字,正是一個指針是0 http://stackoverflow.com/questions/1282295/what-exactly-is-nullptr – 2013-02-18 12:33:37

0

在C++中,有幾種方式表明問題發生在函數中。

您可以返回一個特殊值,調用代碼會將其識別爲無效值。如果函數應該返回一個指針,它可以是一個NULL指針,如果函數返回一個數組中的索引,它可以是一個負值,或者如果是自定義類(例如您的類Entry),則可以定義一個特殊的Entry::invalid值或者可以被調用函數檢測到的類似內容。

調用代碼可能看起來像

if (entryInstance->get_by_last_name("foobar") != Entry::invalid) 
{ 
    // here goes the code for the case where the name is valid 
} else { 
    // here goes the code for the case where the name is invalid 
} 

在另一方面,你可以使用C++異常機制,讓你的函數拋出異常。爲此,您可以創建自己的異常類(或使用標準庫中定義的異常類,從std::exception派生)。你的功能將throw的例外和你的調用代碼將不得不趕上它與try ... catch聲明。

try 
    { 
    entryInstance->get_by_last_name("foobar") 
    } 
    catch (Exception e) 
    { 
    // here goes the code for the case where the name is invalid 
    } 
    // here goes the code for the case where the name is valid 
1

根據你的函數返回類型,拋出一個異常肯定是'正確'的C++事情。

您可能需要這樣的功能來幫助你,但:

bool AddressBook::lastNameExists(const string &last_name) 
{ 
    return addr_map.count(last_name) > 0; 
} 

注意,目前的代碼返回「按價值」,所以修改返回的條目不會更新地圖的條目。不知道這是偶然還是設計...

+0

但現在我們搜索兩次地址簿。一次來測試它是否存在並且一次獲得價值。 – 2013-02-18 15:14:45

+0

@LokiAstari - 在某些情況下,你可能會同意。這並不完美。我真正的觀點是,經常嘗試訪問不存在的映射條目是程序邏輯錯誤(而不是用戶驗證錯誤)。 – Roddy 2013-02-18 15:45:25

2

你的代碼的一些快速註解:

if(this->addr_map.count(last_name) != 0){ 
    //What can I do here? 

你可能想用另一種方式:

if(this->addr_map.count(last_name) == 0){ 
    //handle error 

但你真正的問題就出在這裏:

return addr_map[last_name]; 

兩件事情來請注意:

  1. 地圖的operator[]可以做2件事:如果元素存在,則返回它;如果元素不存在,則創建一個帶有指定鍵和值的默認constructor的新(鍵值)pair。可能不是你想要的。但是,如果您之前的陳述是正確的,那麼後者將永遠不會發生,因爲我們知道關鍵在於事先存在。
    1. 之前在致電count()時,您有效地告訴map嘗試查找元素。通過呼叫operator[],您告訴map再次找到它。所以,你正在做兩次檢索單個值的工作。

一個更好的(快)的方式來做到這涉及到迭代器,以及find方法:

YourMap::iterator it = addr_map.find(last_name); //find the element (once) 
if (it == addr_map.end()) //element not found 
{ 
    //handle error 
} 
return *it.second; //return element 

現在,回到手頭的問題。如果找不到last_name,該怎麼辦? 正如其他答案指出:

  • 最簡單的解決辦法是將返回一個指針(NULL,如果未找到)
  • 使用boost::optional
  • 簡單地返回YourMap::iterator,但它似乎是試圖「」從AddressBook用戶「隱藏」,所以這可能是一個壞主意。
  • throw an exception。但是請等待,現在您必須首先檢查是否調用此方法是「安全」的(或者在適當時處理exception)。此檢查需要一個布爾方法,如調用get_by_last_name之前必須調用的lastNameExists。當然,然後我們回到方塊1.我們執行2次查找操作來檢索單個值。這是安全的,但如果你正在做很多get_by_last_name的調用,那麼這可能是一個用不同的解決方案進行優化的好地方(另外,可以說這個例外並不是非常有建設性的:尋找不存在的東西有什麼問題,嗯?)。
  • Entry表明創建dummy不是一個真正的Entry但這是設計非常差(難以管理,直覺,浪費 - 你的名字)。

正如你所看到的,前兩種解決方案是最好的。

+1

不錯,但是對於「每次調用get_by_last_name都需要用try ... catch塊進行包裝」非常錯誤。你會在'catch'塊中做什麼 - 返回一個錯誤代碼?引發異常?有些情況下,如果無法找到該元素,您可能需要'默默'做某些事情,在這種情況下,'lastNameExists'方法可能是一個有用的補充。 – Roddy 2013-02-18 14:06:45

+0

參見上次編輯,它反映了對try ... catch方法的更仔細研究 – eladidan 2013-02-18 14:21:07

0

除了每個姓氏可以有多個條目的事實。

消除getter,你已經解決了這個問題,或者至少把它轉移到別處了。

告訴AddressBook顯示給定姓氏的人。如果沒有,它什麼都不能做。

AddressBookRenderer renderer; 
AddressBook contacts; 

contacts.renderSurnames("smith", renderer); 
contacts.renderCompletions("sm", renderer); 
//etc 
1

其他答案給出了各種方法,其中大多數都是有效的。我沒有看到這個尚未:

你可以用默認值添加第二個參數:

Entry AddressBook::get_by_last_name(string last_name, const Entry& default_value){ 
    if(this->addr_map.count(last_name) == 0){ 
     return default_value; 
    } else { 
     return addr_map[last_name]; 
    } 

在這種特定情況下,有可能不是一個非明智的默認值現有的姓氏,但在很多情況下都有。

+0

注意:'default_value'應該是Entry類型。 – 2013-02-18 15:15:32

+0

@LokiAstari你說得對,謝謝! – Sjoerd 2013-02-18 19:43:11

0

你可以做什麼std :: map(和其他容器做的)。

您從搜索功能中返回一個迭代器。
如果搜索未找到有用的值,則返回一個迭代器以結束()。

class AddressBook 
{ 
     typedef <Your Container Type> Container; 
    public: 
     typedef Container::iterator iterator; 


     iterator get_by_last_name(std::string const& lastName) {return addr_map.find[lastName];} 

     iterator end()           {return addr_map.end();} 
}; 

您的地址簿是一個像對象一樣的容器。
在搜索中找不到項目很可能會發生,但它沒有足夠的上下文來包含錯誤處理代碼(因爲地址簿可以從很多地方使用,並且每個地方都會有不同的錯誤處理想法)。

因此,您必須將未找到狀態的測試移出通訊簿。
就像「Python」我們返回一個標記。在C++中,這通常是調用代碼可以檢查並採取適當操作的end()的迭代器。

AddressBook& ab = getAddressBookRef(); 
AddressBook::iterator find = ab.get_by_last_name("cpp_hobbyist"); 
if (find != ab.end()) 
{ 
    Entity& person *find; // Here you have a reference to your entity. 
    // you can now manipulate as you want. 
} 
else 
{ 
    // Display appropriate error message 
}