Re: [問題] Private method 該不該確認參數正確性?

看板C_and_CPP作者 (阿貓)時間9年前 (2015/01/27 22:17), 9年前編輯推噓11(11011)
留言22則, 10人參與, 最新討論串2/2 (看更多)
一般來說比較好的方式應該是把參數的condition包在type裡面, 以你的例子來說,sqrt的參數不能<0,那就創一個新的class把他包起來: class non_zero_float { public: non_zero_float(float v_) : v(check(v_)) {} auto operator()() const -> float { return v; } private: auto check(float v) -> float { if (v < 0.0f) { throw std::runtime_error("value < 0.0f"); } else { return v; } } float v; }; 然後你的public/private methods寫成這樣: auto private_sqrt(const non_zero_float nzv) -> float { return std::sqrt(nzv()); } auto public_sqrt(const non_zero_float nzv) -> float { return private_sqrt(nzv); } 呼叫public_sqrt的方式就變成: public_sqrt(non_zero_float(2.0f)) 對使用者來說,要呼叫{public,private}_sqrt都必須建構出一個non_zero_float, 也就是說這個API設計會讓compiler reject錯誤的使用方式(也就是沒有檢查), 在建構時進行參數檢查,而建構完成後該值就不能再更動(immutable)了, 所以接下來使用的時候無論pass給誰,都不需要再額外進行check。 我們甚至可以把上面的作法generalize: template<typename T, typename Predicate> class with_predicate { public: with_predicate(T t_, Predicate pred = Predicate()) : t(pred(t_)) {} auto operator()() const -> const T& { return t; } private: T t; }; 而上面的non_zero_float就可以寫成: struct non_zero_predicate { auto operator()(const float v) -> float { if (v < 0.0f) { throw std::runtime_error("value < 0.0f"); } else { return v; } } }; struct non_zero_float : with_predicate<float, non_zero_predicate> { non_zero_float(const float v) : with_predicate<float, non_zero_predicate>(v) {} }; 或是要接受一個已經sort好的std::vector<int>: struct is_sorted_predicate { template<typename Container> auto operator()(const Container& c) -> const Container& { if (std::is_sorted(std::begin(c), std::end(c))) { return c; } else { throw std::runtime_error("container is not sorted"); } } }; struct sorted_int_vector : with_predicate<std::vector<int>, is_sorted_predicate> { sorted_int_vector(const std::vector<int>& v) : with_predicate<std::vector<int>, is_sorted_predicate>(v) {} }; 當然你也可以讓Predicate不要throw exception,直接想辦法return一個正確的值, (或許這個function object不要叫Predicate比較好...) 舉個實際的應用來說, 在寫3D math的函式時,可以把三維向量(vec3)和三維的單位向量(uvec3)分開, 而uvec3只能透過vec3做normalize後才能建構出來, auto normalize(vec3 v) -> uvec3; 計算時如果需要input為單位向量,就宣告input的型態為uvec3, 這樣如果使用的時候不小心把不是單位向量的vec3傳過去,compiler時就會出錯 這個概念甚至可以做更多延伸,例如把點(point)和向量(vec)的概念分開, 然後僅支援合理的運算,例如: point + vector -> point point - point -> vector 而如果寫出例如「point + point」這種沒定義的運算就會在編譯時產生錯誤 其實有很多這種API design的技巧可以運用compiler來限制API正確的使用方式, 多想幾分鐘你可以不用呆呆的一直用float*然後還要花時間加上註解說明, C++的class不是只是用來把data和method綁在一起讓你可以少傳一個this而已 ------------------------------------------------------------------------------ 題外話: 這種type + logical predicate的模式在程式語言學中稱作refinement types, 舉個例子來說,假設f1的參數要求必須是介於40和60之間的int, 我們可以把這個參數乘二後傳給另一個要求參數介於0~100之間的f2嗎? 如果用上面C++的寫法,可能必須定義出各種數值區間的型態以及他們之間的各種轉換, 數量一多programmer根本無法去做處理,所以只能應用在一些簡單的case上, 但這個例子如果在支援refinement types的LiquidHaskell底下,就會寫成: {-@ f1 :: { n : Int | n >= 40 && n <= 60 } -> () @-} f1 :: Int -> () f1 n = f2 (n + n) {-@ f2 :: { n : Int | n >= 0 && n <= 100 } -> () @-} f2 :: Int -> () f2 n = () 編譯器會用SMT solver幫你證明f1的參數可以傳給f2, 但很明顯這個程式是有問題的(n > 50就會超出f2的範圍),所以編譯時會直接產生錯誤, 而如果將上述f2的條件改成n >= 80 && n <= 120這樣編譯時就沒有問題 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 140.113.88.55 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1422368253.A.8D6.html

01/27 23:52, , 1F
這個給個推 也是個非常不錯的方案
01/27 23:52, 1F

01/27 23:52, , 2F
尤其C++有template 有各種奇奇怪怪的overload
01/27 23:52, 2F

01/27 23:58, , 3F
不過我會建議你再增加一個explicit operator float()
01/27 23:58, 3F

01/27 23:58, , 4F
這樣會更方便 可以直接把這個class當float來用
01/27 23:58, 4F

01/27 23:59, , 5F
不過請務必不能漏掉explicit 不然他轉型很難控制
01/27 23:59, 5F

01/28 00:22, , 6F
喔對 用explicit operator float應該會比較好
01/28 00:22, 6F

01/28 00:22, , 7F
剛剛第一個想到是用operator()應該是我最近scala寫太多=.=
01/28 00:22, 7F

01/28 00:38, , 8F
scala xD
01/28 00:38, 8F
※ 編輯: PkmX (140.113.88.55), 01/28/2015 00:46:06

01/28 05:00, , 9F
推,學到一課
01/28 05:00, 9F

01/28 10:46, , 10F
感覺是個不錯的做法, Secure Coding in C 也有講到
01/28 10:46, 10F

01/28 10:46, , 11F
Refinement types
01/28 10:46, 11F

01/28 11:17, , 12F
關於下面的是否可定義 ValidInt(int n, irange r) 然後
01/28 11:17, 12F

01/28 11:18, , 13F
實作 ValidInt::Multiply 之類的呢?
01/28 11:18, 13F

01/28 11:20, , 14F
好像漏了一點,大概知道難度在哪...
01/28 11:20, 14F

01/28 14:58, , 15F
高手
01/28 14:58, 15F

01/28 17:26, , 16F
推一下,不過單是看code感覺就眼花了,有點好奇實務上
01/28 17:26, 16F

01/28 17:26, , 17F
真的會有不少人這樣檢查嗎OAO
01/28 17:26, 17F

01/28 17:28, , 18F
結果說要推卻忘記推,補推一下(囧)
01/28 17:28, 18F

01/29 00:05, , 19F
學習學習
01/29 00:05, 19F

01/29 11:36, , 20F
實務上就是一個template而已 檢查條件可以用policy
01/29 11:36, 20F

01/29 11:36, , 21F
達成,其實這個已經算是夠完整的實作了
01/29 11:36, 21F

02/12 16:23, , 22F
強大!!
02/12 16:23, 22F
文章代碼(AID): #1KnvtzZM (C_and_CPP)
文章代碼(AID): #1KnvtzZM (C_and_CPP)