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

作者: PkmX (阿貓)   2015-01-27 22:17:31
一般來說比較好的方式應該是把參數的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而已
作者: Killercat (殺人貓™)   2015-01-27 23:52:00
這個給個推 也是個非常不錯的方案尤其C++有template 有各種奇奇怪怪的overload不過我會建議你再增加一個explicit operator float()這樣會更方便 可以直接把這個class當float來用不過請務必不能漏掉explicit 不然他轉型很難控制
作者: PkmX (阿貓)   2015-01-28 00:22:00
喔對 用explicit operator float應該會比較好剛剛第一個想到是用operator()應該是我最近scala寫太多=.=
作者: suhorng ( )   2015-01-28 00:38:00
scala xD
作者: Caesar08 (Caesar)   2015-01-28 05:00:00
推,學到一課
作者: Ebergies (火神)   2015-01-28 10:46:00
感覺是個不錯的做法, Secure Coding in C 也有講到Refinement types
作者: BlazarArc (Midnight Sun)   2015-01-28 11:17:00
關於下面的是否可定義 ValidInt(int n, irange r) 然後實作 ValidInt::Multiply 之類的呢?好像漏了一點,大概知道難度在哪...
作者: lc85301 (pomelocandy)   2015-01-28 14:58:00
高手
作者: a27417332 (等號卡比)   2015-01-28 17:26:00
推一下,不過單是看code感覺就眼花了,有點好奇實務上真的會有不少人這樣檢查嗎OAO結果說要推卻忘記推,補推一下(囧)
作者: carylorrk (carylorrk)   2015-01-29 00:05:00
學習學習
作者: Killercat (殺人貓™)   2015-01-29 11:36:00
實務上就是一個template而已 檢查條件可以用policy達成,其實這個已經算是夠完整的實作了
作者: FukadaKyoko (小毛哥)   2015-02-12 16:23:00
強大!!

Links booklink

Contact Us: admin [ a t ] ucptt.com