看板 C_and_CPP 關於我們 聯絡資訊
※ 引述《nullpointerk (打滾貓)》之銘言: : 完成後,下面的程式碼就都可以運作了,在瀏覽器上跑跑看吧 : https://godbolt.org/z/wrd7Rv : 這樣就優雅解決原 PO 的煩惱了:如果以後要讀更多ID我就無解了 看到很恐怖的 code, 推文可能不是那麼清楚就直接發一篇了 你的程式碼有幾個問題, 有大有小, 後面我列舉出來說明. namespace collision 一般在開發的時候應該避免把東西放進命名空間 std 裡, 這可能導 致 resolve name 時會模棱兩可. 再來因為參數型別本身不定義在 std 內, 所以無法用 ADL (Argument-Dependent Lookup) 來找到這 個函式 namespace std { std::string to_string(const std::string::value_type c) { return {c}; } } // namespace std 比較好的做法就是呼叫的時候不使用 qualified name, 透過 using 把候選名單拉進來 using std::to_string, ::to_string; auto to = to_string(from); 在使用 std::begin() / std::end() 等函式的時候也是一樣, 為了 保有更改容器的彈性, 我們不會用 qualified name, 而是先把可能 的名稱先 using 進來, 編譯器首先會透過 ADL 找尋可用的版本 ( 最好是 overloading), 找不到才會回來用 std 底下的模板來具現 化呼叫實體 (因為後者優先權低). 不過在 C++20 以後開始全面引進 CPO (Customization Point Object) 的概念, 上述所提的 std 函式將會提升為 functor, 在它 們的 call operator 內還是會透過 ADL 找尋呼叫實體, 在那之後 是不是用 qualified name 就不是那麼重要了. 濫用 uniform initialization uniform initialization 原本是用來消歧異的手段, 但在錯誤的情 境下使用反而會產生語義不明, 例如和 list initialization 混淆 . 如果類別也允許使用 std::initializer_list 來做初始化, 那麼 在接受多個引數的時候你怎麼知道被呼叫的建構子是哪版? 這在 C++17 引進 CTAD (Class Template Argument Deduction) 之後讓 情況更惡化了, 而且 uniform initialization 不允許 narrowing conversion, 搞清楚意圖再使用會比較好. Barry Revzin: Uniform initialization isn’t https://bit.ly/2X91QDS 順帶一提, 你可以加上編譯器選項 --pedantic-errors 看看有無誤 解使用方法. 濫用 temporary object lifetime extension 根據 [class.temporary] 2 的描述: 2. The materialization of a temporary object is generally delayed as long as possible in order to avoid creating unnecessary temporary objects. [ Note: Temporary objects are materialized: (2.1) when binding a reference to a prvalue 這邊其中一項就是綁定參考的情況, 但這僅限於直接綁定, 間接綁 定是不會獲得再延長效果的. 在這裡不得不提設計不良的 std::min() 它的宣告如下: template< class T > const T& min( const T& a, const T& b ); 因為參數型別是 ref to const, 所以可以接受 prvalue 做為引數, 而回傳值只是把 ref 再傳出去而已, 這裡的用法和 ?: 運算子差不 多, 不過因為引數的 lifetime 只被延長到函式呼叫結束為止, 所 以回傳值其實是 dangling reference. 而且在 C++17 以後保證了 copy elision, 所以已經不需要刻意這樣做. 濫用 decltype 雖然 decltype 運算子好像可以省下未來修改程式碼的功, 但有沒 有想過 decltype 推導出來的型別如果是 ref to const 該怎麼辦? 有沒有用 type traits 來過濾掉不合法的情況? reference to small object reference 通常會以類似 pointer 的方式去實作, 當呼叫函式使用 pass by reference 傳遞引數的時候首先要考慮的是: 它的成本是 否低於 pass by value? 在 C++ Core Guidelines 有給出一個參考 F.16: For “in” parameters, pass cheaply-copied types by value and others by reference to const https://bit.ly/39AE728 為了 8 位元物件參數用 pass by reference 是顯然不合理的. 意義不明的 forwarding reference 函式 unkonwn_logic_a() 裡的參數 ids 型別被定義為 forwarding reference. template <typename IDS> std::string unkonwn_logic_a( decltype(length(std::declval<IDS>())) pcs, IDS &&ids ); 通常我們使用 fwding ref 是用來保留引數的值類別 (value category), 接著用同樣的類別來選擇可用的多載函式呼叫, 如 length(ids) 呼叫. 不過因為 ref 被綁定之後會變 lvalue ref , 這裡還需要使用 std::forward() 把值類別加回來才行. 所以正 確的呼叫應為 length(std::forward<IDS>(ids)); 但因為你壓根沒 有提供其他接受 rvalue 版本的 length() 函式, 所以在這裡用 fwding reference 是多餘的. 後話 通常我們在寫類別/函式模板時, 情況都是: 因為有複數個實作非常相近, 所以把它們合併在一起 而不是為了泛用而去寫模板, 這容易因為沒仔細檢查 type contraint 而寫出很難改的模板. 有個準則就是你有沒有透過 SFINAE (Substitution Failure Is Not An Error) 來限縮模板可 用情境. 因為模板內的操作都是基於某種假設而實作, 像是需要有 可被呼叫的成員函式等等, 不符合假設的型別本就不該拿來具現化 造成編譯錯誤. 有一個可能比較少用的標頭擋 <iterator> 裡面像是 std::size() 就跟你的 length() 角色是一樣的, 在實作之前看看標準函式庫的 例子就能多少避掉上面提到的問題. -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 223.136.172.53 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1585982593.A.3A8.html ※ 編輯: loveme00835 (223.136.172.53 臺灣), 04/04/2020 15:21:45
sarafciel: 推,放個假回來就看到兩篇好文章 04/06 00:59
flysonics: 推 04/06 18:57
s4300026: 推 04/07 08:27