Re: [問題] Private method 該不該確認參數正確性?
一般來說比較好的方式應該是把參數的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
01/27 23:52, 2F
推
01/27 23:58, , 3F
01/27 23:58, 3F
→
01/27 23:58, , 4F
01/27 23:58, 4F
→
01/27 23:59, , 5F
01/27 23:59, 5F
→
01/28 00:22, , 6F
01/28 00:22, 6F
→
01/28 00:22, , 7F
01/28 00:22, 7F
→
01/28 00:38, , 8F
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
01/28 10:46, 10F
→
01/28 10:46, , 11F
01/28 10:46, 11F
推
01/28 11:17, , 12F
01/28 11:17, 12F
→
01/28 11:18, , 13F
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
01/28 17:26, 16F
→
01/28 17:26, , 17F
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
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
討論串 (同標題文章)
完整討論串 (本文為第 2 之 2 篇):