2012-03-05 19 views
15

我將從示例開始。 boost中有一個很好的「tokenizer」類。它有一個字符串被標記化如在構造函數的參數:在構造函數中傳遞指針/引用到現有對象的首選方式是什麼?

std::string string_to_tokenize("a bb ccc ddd 0"); 
boost::tokenizer<boost::char_separator<char> > my_tok(string_to_tokenize); 
/* do something with my_tok */ 

字符串未在標記生成器修飾的,所以它是由常量對象引用傳遞。因此,我可以在那裏傳遞一個臨時對象:

boost::tokenizer<boost::char_separator<char> > my_tok(std::string("a bb ccc ddd 0")); 
/* do something with my_tok */ 

一切都看起來不錯,但如果我嘗試使用標記生成器,災難發生時。經過簡短的調查,我意識到,tokenizer類存儲了我給它的引用,並用於進一步的使用。當然,它不適合引用臨時對象。

該文檔沒有明確表示,在構造函數中傳遞的對象將在稍後使用,但好吧,也沒有說明,它不會是:)所以我不能假設這是我的錯誤。

但是有點令人困惑。在一般情況下,當一個對象通過const引用接受另一個對象時,它表明可以在那裏給出臨時對象。你怎麼看?這是一個糟糕的習俗嗎?在這種情況下應該使用指向對象的指針(而不是引用)?或者甚至更進一步 - 有一些特殊的關鍵字來允許/禁止給予臨時對象作爲參數是否有用?

編輯:文檔(1.49版)是相當簡約,並且可以建議這樣的問題的唯一部分是:

注:沒有解析是在建設實際進行。解析是按需完成的,因爲令牌是通過begin提供的迭代器訪問的。

但它沒有明確說明,將使用相同的對象。

但是,這個問題的重點是在這種情況下對編碼風格的討論,這只是一個啓發我的例子。

+0

被同樣的事情咬傷。當我可以的時候,我使用boost :: ref作爲ctor arg現在至少提示參考將被存儲 – Anycorn 2012-03-05 11:07:30

+0

如果在boost :: tokenizer中確實存在這樣的錯誤,我會感到驚訝。 – CashCow 2012-03-05 11:08:28

+0

@CashCow:它更多的是在文檔中的一個bug,因爲'tokenizer'在它的生命週期中保持對構造函數參數的引用,這對於臨時對象來說是地獄般的... – 2012-03-05 13:04:52

回答

8

就我個人而言,我認爲這是一個壞主意,最好是寫構造函數來複制字符串,或者採取const std::string*來代替。這只是一個額外的字符供打電話者輸入,但該字符會偶然使用暫時停止。

作爲一項規則:不要爲維護對象創建責任,也不要明確表示他們有責任。

我認爲一個特殊的關鍵字不足以證明改變語言的正確性。實際上並不是臨時性問題,而是任何物體的生命週期都比構造物體的時間更少。在某些情況下,臨時會很好(例如,如果tokenizer對象本身在同一個完整表達式中也是臨時的)。爲了修復一半的問題,我並不想爲這種語言而煩惱,並且有更全面的修復方法(例如,採取shared_ptr,儘管它有自己的問題)。

「所以我無法承擔這個,我的錯誤」

我不認爲這真的是你的錯,我Frerich同意,以及作爲對我的個人風格指南,如果做到這一點在所有的,你這樣做,不要文件,那麼這是任何合理的風格指南中的文檔錯誤。

如果它是「至少和函數調用一樣長」以外的其他任何東西,那麼必須記錄引用函數參數的所需生命週期。這是文檔通常會鬆懈的問題,需要妥善處理以避免錯誤。

即使在垃圾收集的語言,其中的壽命本身是自動處理的,因此往往會得到忽視,它很重要,你是否能夠更改或重新使用你的對象不改變,你通過它的一些其他對象的行爲到過去的某個時間的方法。因此,函數應該記錄它們是否保留了其參數的別名任何語言缺乏參考透明度。特別是在C++中,對象生命週期是調用者的問題。

不幸的是,實際上確保你的函數不能保留一個引用的唯一機制是傳遞值,它具有性能成本。如果你可以發明一種允許別名正常使用的語言,但也有一種在編譯時強制執行的C風格的屬性(const風格),以防止函數將參數引用給它們的參數,然後祝你好運並簽名。

+0

我同意你的看法,並且感到驚訝,這讓它變得更好。我會通過非const引用來獲取參數,這可以確保用戶不會傳入臨時文件。我也可以存儲一個std :: string成員並與傳入的參數交換,並讓用戶創建自己的副本,如果他們想要他們的原始字符串。 – CashCow 2012-03-05 14:40:33

+0

元評論:爲什麼是社區維基答案?在任何情況下,我+1。 – Francesco 2012-03-05 15:49:15

+0

@Francesco:我已經或多或少地放棄了試圖找出SO的主題,這是其他人決定的。但我通常不會聽取意見問題的代表。 – 2012-03-05 16:33:45

11

如果一些功能(例如,構造函數)接受一個參數作爲參考給const那麼它應該要麼

  • 文獻明確地被引用的對象的生存期必須滿足某些要求(如 「是不是在此之前摧毀,這種情況發生」)

  • 如果需要在稍後使用給定的對象,則在內部創建副本。

在這種特定的情況下(在boost::tokenizer類)我假定後者沒有出於性能原因完成和/或使類可用與容器類型其不是在首位甚至能夠複製。出於這個原因,我認爲這是一個文檔錯誤。

3

正如其他人所說的,boost::tokenizer示例是tokenizer中的錯誤或文檔中缺少警告的結果。

要普遍回答這個問題,我發現以下優先列表有用。如果出於某種原因無法選擇某個選項,則轉到下一項。

  1. 通過值(以可接受的成本和可複製也不需要改變原來的對象)由const引用
  2. 通行證(不需要改變原來的對象)由參考
  3. 山口(需要改變原來的對象)
  4. 路過的shared_ptr(該對象的生命週期是由別的管理,這也清楚地表明瞭打算繼續參考)
  5. 路過原始指針(你必須轉換爲一個地址,或者出於某種原因你不能使用智能指針)

此外,如果您選擇列表中下一項的理由是「表現」,那麼請坐下來衡量差異。根據我的經驗,大多數人(尤其是Java或C#背景的人)傾向於高估通過值傳遞對象的成本(並且低估了取消引用的成本)。按價值傳遞是最安全的選擇(它不會在對象或函數之外引起任何意外,即使在另一個線程中也是如此),不要輕易放棄這個巨大優勢。

1

很多時候它將取決於上下文,例如,如果它是一個將在for_each或類似函數中調用的函數,那麼您通常會將函數中的引用或指針存儲到您期望的對象中一輩子超越你的功能。

如果它是一般用途類,那麼你必須考慮人們將如何使用它。

如果你正在編寫一個標記器,你需要考慮複製你正在標記的東西可能會很昂貴,但是你還需要考慮如果你正在編寫一個boost庫,你會爲普通公衆編寫它,以多用途的方式使用它。

存儲一個const char *會比這裏的std::string const&好。如果用戶有std::string,那麼const char *將保持有效,只要他們不修改他們的字符串,他們可能不會。如果他們有一個const char *或者包含一個字符數組並且將其傳入的東西,它將會複製它以創建std::string const &,並且您將面臨生命危險,因爲它不會超出您的構造函數。

當然,使用const char *你不能在你的實現中使用所有可愛的std::basic_string函數。

有一個選項可以作爲參數,採用std::string&(不是const引用),它應該保證(通過一個兼容的編譯器)沒有人會臨時通過,但是你將能夠證明你沒有實際上改變它,以及你看似不是const正確的代碼背後的基本原理。請注意,我在代碼中也曾使用過這個技巧。你可以愉快地使用字符串的查找功能。 (以及,如果你願意,採取basic_string而不是字符串,所以你可以標記寬字符串)。

+0

我同意非const引用將有助於避免這種錯誤,但這是一種解決方法。這當然不是一個乾淨的解決方案,是嗎? – peper0 2012-03-06 06:51:35

+0

在我看來,「const char *」在大多數情況下都不能解決問題,因爲我仍然可以傳遞引用臨時對象的string(something).c_str()。 – peper0 2012-03-06 06:55:34

相關問題