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