C ++函数参数安全

在一个需要几个相同types参数的函数中,我们如何保证调用者不会搞乱顺序?

例如

void allocate_things(int num_buffers, int pages_per_buffer, int default_value ... 

然后

 // uhmm.. lets see which was which uhh.. allocate_things(40,22,80,... 

一个典型的解决scheme是将参数放在一个结构中,并带有命名字段。

 AllocateParams p; p.num_buffers = 1; p.pages_per_buffer = 10; p.default_value = 93; allocate_things(p); 

当然,你不必使用字段。 你可以使用成员函数或任何你喜欢的。

如果您有C ++ 11编译器,则可以将用户定义的文字与用户定义的types结合使用。 这是一个天真的做法:

 struct num_buffers_t { constexpr num_buffers_t(int n) : n(n) {} // constexpr constructor requires C++14 int n; }; struct pages_per_buffer_t { constexpr pages_per_buffer_t(int n) : n(n) {} int n; }; constexpr num_buffers_t operator"" _buffers(unsigned long long int n) { return num_buffers_t(n); } constexpr pages_per_buffer_t operator"" _pages_per_buffer(unsigned long long int n) { return pages_per_buffer_t(n); } void allocate_things(num_buffers_t num_buffers, pages_per_buffer_t pages_per_buffer) { // do stuff... } template <typename S, typename T> void allocate_things(S, T) = delete; // forbid calling with other types, eg. integer literals int main() { // now we see which is which ... allocate_things(40_buffers, 22_pages_per_buffer); // the following does not compile (see the 'deleted' function): // allocate_things(40, 22); // allocate_things(40, 22_pages_per_buffer); // allocate_things(22_pages_per_buffer, 40_buffers); } 

到目前为止,还有两个很好的答案:另外一个方法是尽可能地利用types系统,创build强大的typedef。 例如,使用boost strong typedef( http://www.boost.org/doc/libs/1_61_0/libs/serialization/doc/strong_typedef.html )。

 BOOST_STRONG_TYPEDEF(int , num_buffers); BOOST_STRONG_TYPEDEF(int , num_pages); void func(num_buffers b, num_pages p); 

以错误的顺序调用函数的参数现在将是一个编译错误。

一些关于这个的笔记。 首先,boost的强types定义在方法上相当过时; 你可以用可变参数CRTP做更好的事情,并完全避免macros。 其次,显然这会引入一些开销,因为你经常需要明确地转换。 所以一般你不想过度使用它。 在图书馆里反复出现的东西真的很好。 对于那些作为一个单独的东西来说不是那么好。 所以举个例子,如果你正在编写一个GPS库,你应该有一个强大的双重typedef以米为单位的距离,一个强大的int64 typedef以前的时间(以纳秒为单位),等等。

(注:post最初被标记为“C”)

C99以上允许扩展到@Dietrich Epp的想法:复合文字

 struct things { int num_buffers; int pages_per_buffer; int default_value }; allocate_things(struct things); // Use a compound literal allocate_things((struct things){.default_value=80, .num_buffers=40, .pages_per_buffer=22}); 

甚至可以通过结构的地址。

 allocate_things(struct things *); // Use a compound literal allocate_things(&((struct things){.default_value=80,.num_buffers=40,.pages_per_buffer=22})); 

你不能。 这就是为什么build议尽可能less的函数参数。

在你的例子中,你可以有单独的函数,如set_num_buffers(int num_buffers)set_pages_per_buffer(int pages_per_buffer)

你可能已经注意到你自己allocate_things不是一个好名字,因为它没有expression这个函数实际在做什么。 特别是我不希望它设置一个默认值。

你真的会尝试QA任意整数的所有组合? 并抛出所有的负值/零值检查等?

只需为最小,中等和最大缓冲区数量以及小中大型缓冲区大小创build两个枚举types。 然后让编译器完成这个工作,让你的QA人员rest一下:

 allocate_things(MINIMUM_BUFFER_CONFIGURATION, LARGE_BUFFER_SIZE, 42); 

那么你只需要testing有限数量的组合,你就会有100%的覆盖率。 从现在开始的5年工作的人只需要知道他们想要达到的目标,而不必猜测他们可能需要的数字或者实际上已经在现场testing了哪些数值。

它确实使得代码稍微难以扩展,但是听起来这些参数是用于低级别的性能调优的,所以调用这些值不应该被认为是廉价/不重要的,也不需要彻底的testing。 从allocate_something(25,25,25)的变化的代码审查;

…至

allocate_something(30,80,42);

…可能只是耸了耸肩,但一个新的枚举值EXTRA_LARGE_BUFFERS的代码审查可能会触发关于内存使用,文档,性能testing等所有正确的讨论。

只是为了完整性,当你的调用变成时,你可以使用命名参数

 void allocate_things(num_buffers=20, pages_per_buffer=40, default_value=20); // or equivalently void allocate_things(pages_per_buffer=40, default_value=20, num_buffers=20); 

然而,使用当前的C ++,这需要相当多的代码来实现(在头文件中声明allocate_things() ,它还必须声明适当的外部对象num_buffers等,并提供operator=返回唯一合适的对象。

———-工作示例(对于sergej)

 #include <iostream> struct a_t { int x=0; a_t(int i): x(i){} }; struct b_t { int x=0; b_t(int i): x(i){} }; struct c_t { int x=0; c_t(int i): x(i){} }; // implement using all possible permutations of the arguments. // for many more argumentes better use a varidadic template. void func(a_t a, b_t b, c_t c) { std::cout<<"a="<<ax<<" b="<<bx<<" c="<<cx<<std::endl; } inline void func(b_t b, c_t c, a_t a) { func(a,b,c); } inline void func(c_t c, a_t a, b_t b) { func(a,b,c); } inline void func(a_t a, c_t c, b_t b) { func(a,b,c); } inline void func(c_t c, b_t b, a_t a) { func(a,b,c); } inline void func(b_t b, a_t a, c_t c) { func(a,b,c); } struct make_a { a_t operator=(int i) { return {i}; } } a; struct make_b { b_t operator=(int i) { return {i}; } } b; struct make_c { c_t operator=(int i) { return {i}; } } c; int main() { func(b=2, c=10, a=42); }